diff --git a/libcxx/benchmarks/vector_operations.bench.cpp b/libcxx/benchmarks/vector_operations.bench.cpp --- a/libcxx/benchmarks/vector_operations.bench.cpp +++ b/libcxx/benchmarks/vector_operations.bench.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -36,5 +37,25 @@ std::vector{}, getRandomStringInputs)->Arg(TestNumInputs); +template +void BM_VectorPushBack(benchmark::State& state) { + T x = {}; + std::vector xs = {}; + for (auto _ : state) { + benchmark::DoNotOptimize(x); + xs.push_back(std::move(x)); + benchmark::DoNotOptimize(xs); + } +} + +// Nontrivial, but trivial for calls --> trivially relocatable. +struct [[clang::trivial_abi]] Nontrivial { + int* x = nullptr; + ~Nontrivial() { benchmark::DoNotOptimize(*this); } +}; + +BENCHMARK_TEMPLATE(BM_VectorPushBack, int*); +BENCHMARK_TEMPLATE(BM_VectorPushBack, std::unique_ptr); +BENCHMARK_TEMPLATE(BM_VectorPushBack, Nontrivial); BENCHMARK_MAIN(); diff --git a/libcxx/include/__memory/allocator_traits.h b/libcxx/include/__memory/allocator_traits.h --- a/libcxx/include/__memory/allocator_traits.h +++ b/libcxx/include/__memory/allocator_traits.h @@ -396,6 +396,17 @@ : __is_cpp17_move_insertable<_Alloc> { }; +// __has_default_allocator_construct +template +struct __has_default_allocator_construct + : integral_constant::value || !__has_construct<_Alloc, _Tp*, _Args...>::value) > {}; + +// __has_default_allocator_destroy +template +struct __has_default_allocator_destroy + : integral_constant::value || !__has_destroy<_Alloc, _Tp*>::value) > {}; + #undef _LIBCPP_ALLOCATOR_TRAITS_HAS_XXX _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/memory b/libcxx/include/memory --- a/libcxx/include/memory +++ b/libcxx/include/memory @@ -853,32 +853,36 @@ _LIBCPP_BEGIN_NAMESPACE_STD +// __construct_forward_with_exception_guarantees(__a, __begin1, __end1, __begin2, __use_memcpy) +// +// If __use_memcpy is true_type, this constructs using memcpy. Otherwise, it uses the move constructor. + template _LIBCPP_INLINE_VISIBILITY -void __construct_forward_with_exception_guarantees(_Alloc& __a, _Ptr __begin1, _Ptr __end1, _Ptr& __begin2) { +void __construct_forward_with_exception_guarantees(_Alloc& __a, _Ptr __begin1, _Ptr __end1, _Ptr& __begin2, false_type) { static_assert(__is_cpp17_move_insertable<_Alloc>::value, "The specified type does not meet the requirements of Cpp17MoveInsertable"); - typedef allocator_traits<_Alloc> _Traits; + using _Traits = allocator_traits<_Alloc>; for (; __begin1 != __end1; ++__begin1, (void)++__begin2) { - _Traits::construct(__a, _VSTD::__to_address(__begin2), + _Traits::construct(__a, std::__to_address(__begin2), #ifdef _LIBCPP_NO_EXCEPTIONS - _VSTD::move(*__begin1) + std::move(*__begin1) #else - _VSTD::move_if_noexcept(*__begin1) + std::move_if_noexcept(*__begin1) #endif ); } } -template ::value || !__has_construct<_Alloc, _Tp*, _Tp>::value) && - is_trivially_move_constructible<_Tp>::value ->::type> +template _LIBCPP_INLINE_VISIBILITY -void __construct_forward_with_exception_guarantees(_Alloc&, _Tp* __begin1, _Tp* __end1, _Tp*& __begin2) { +void __construct_forward_with_exception_guarantees(_Alloc&, _Ptr __begin1, _Ptr __end1, _Ptr& __begin2, true_type) { + using _Tp = typename iterator_traits<_Ptr>::value_type; + // TODO: after const value types are no longer supported, remove the const_cast. + using _Vp = typename remove_const<_Tp>::type; ptrdiff_t _Np = __end1 - __begin1; if (_Np > 0) { - _VSTD::memcpy(__begin2, __begin1, _Np * sizeof(_Tp)); + std::memcpy(const_cast<_Vp*>(std::__to_address(__begin2)), std::__to_address(__begin1), _Np * sizeof(_Tp)); __begin2 += _Np; } } @@ -888,7 +892,7 @@ void __construct_range_forward(_Alloc& __a, _Iter __begin1, _Iter __end1, _Ptr& __begin2) { typedef allocator_traits<_Alloc> _Traits; for (; __begin1 != __end1; ++__begin1, (void) ++__begin2) { - _Traits::construct(__a, _VSTD::__to_address(__begin2), *__begin1); + _Traits::construct(__a, std::__to_address(__begin2), *__begin1); } } @@ -899,45 +903,44 @@ typename enable_if< is_trivially_copy_constructible<_Dest>::value && is_same<_RawSource, _RawDest>::value && - (__is_default_allocator<_Alloc>::value || !__has_construct<_Alloc, _Dest*, _Source&>::value) + __has_default_allocator_construct<_Alloc, _Dest, _Source>::value >::type> _LIBCPP_INLINE_VISIBILITY void __construct_range_forward(_Alloc&, _Source* __begin1, _Source* __end1, _Dest*& __begin2) { ptrdiff_t _Np = __end1 - __begin1; if (_Np > 0) { - _VSTD::memcpy(const_cast<_RawDest*>(__begin2), __begin1, _Np * sizeof(_Dest)); + std::memcpy(const_cast<_RawDest*>(__begin2), __begin1, _Np * sizeof(_Dest)); __begin2 += _Np; } } template _LIBCPP_INLINE_VISIBILITY -void __construct_backward_with_exception_guarantees(_Alloc& __a, _Ptr __begin1, _Ptr __end1, _Ptr& __end2) { +void __construct_backward_with_exception_guarantees(_Alloc& __a, _Ptr __begin1, _Ptr __end1, _Ptr& __end2, false_type) { static_assert(__is_cpp17_move_insertable<_Alloc>::value, "The specified type does not meet the requirements of Cpp17MoveInsertable"); typedef allocator_traits<_Alloc> _Traits; while (__end1 != __begin1) { - _Traits::construct(__a, _VSTD::__to_address(__end2 - 1), + _Traits::construct(__a, std::__to_address(__end2 - 1), #ifdef _LIBCPP_NO_EXCEPTIONS - _VSTD::move(*--__end1) + std::move(*--__end1) #else - _VSTD::move_if_noexcept(*--__end1) + std::move_if_noexcept(*--__end1) #endif ); --__end2; } } -template ::value || !__has_construct<_Alloc, _Tp*, _Tp>::value) && - is_trivially_move_constructible<_Tp>::value ->::type> +template _LIBCPP_INLINE_VISIBILITY -void __construct_backward_with_exception_guarantees(_Alloc&, _Tp* __begin1, _Tp* __end1, _Tp*& __end2) { +void __construct_backward_with_exception_guarantees(_Alloc&, _Ptr __begin1, _Ptr __end1, _Ptr& __end2, true_type) { + using _Tp = typename iterator_traits<_Ptr>::value_type; + using _Vp = typename remove_const<_Tp>::type; ptrdiff_t _Np = __end1 - __begin1; __end2 -= _Np; if (_Np > 0) - _VSTD::memcpy(static_cast(__end2), static_cast(__begin1), _Np * sizeof(_Tp)); + std::memcpy(const_cast<_Vp*>(std::__to_address(__end2)), std::__to_address(__begin1), _Np * sizeof(_Tp)); } struct __destruct_n @@ -1098,7 +1101,6 @@ } }; - _LIBCPP_END_NAMESPACE_STD #if defined(_LIBCPP_HAS_PARALLEL_ALGORITHMS) && _LIBCPP_STD_VER >= 17 diff --git a/libcxx/include/type_traits b/libcxx/include/type_traits --- a/libcxx/include/type_traits +++ b/libcxx/include/type_traits @@ -1699,8 +1699,8 @@ template struct is_nothrow_convertible : _Or< - _And, is_void<_Fm>>, - _Lazy<_And, is_convertible<_Fm, _To>, __is_nothrow_convertible_helper<_Fm, _To>> + _And, is_void<_Fm> >, + _Lazy<_And, is_convertible<_Fm, _To>, __is_nothrow_convertible_helper<_Fm, _To> > >::type { }; template @@ -3099,6 +3099,17 @@ inline constexpr bool is_trivially_destructible_v = is_trivially_destructible<_Tp>::value; #endif +// __libcpp_is_trivially_relocatable + +#if __has_keyword(__is_trivially_relocatable) +template +using __libcpp_is_trivially_relocatable = _BoolConstant<__is_trivially_relocatable(_Tp)>; +#else +template +using __libcpp_is_trivially_relocatable = _And::type>, + is_trivially_destructible::type> >; +#endif + // is_nothrow_constructible #if __has_keyword(__is_nothrow_constructible) diff --git a/libcxx/include/vector b/libcxx/include/vector --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -321,6 +321,22 @@ _LIBCPP_BEGIN_NAMESPACE_STD +// Whether vector can trivially relocate a type, with this allocator. +template +using __vector_trivial_relocate = _BoolConstant<_And<__libcpp_is_trivially_relocatable<_Tp>, + __has_default_allocator_construct<_Allocator, _Tp, _Tp&&>, + __has_default_allocator_destroy<_Allocator, _Tp> >::value >; + +// Whether to use trivial destruction (i.e. no destructor) during a relocation operation. +template +using __vector_relocate_trivial_destroy = __vector_trivial_relocate<_Tp, _Allocator>; + +// Whether to use trivial moves (memcpy) during a relocation operation. +template +using __vector_relocate_trivial_move = _BoolConstant<_Or<__vector_trivial_relocate<_Tp, _Allocator>, + _And, + __has_default_allocator_construct<_Allocator, _Tp, _Tp&&> > >::value >; + template */> class _LIBCPP_TEMPLATE_VIS vector { @@ -694,6 +710,9 @@ void __swap_out_circular_buffer(__split_buffer& __v); pointer __swap_out_circular_buffer(__split_buffer& __v, pointer __p); void __move_range(pointer __from_s, pointer __from_e, pointer __to); + template + _LIBCPP_HIDE_FROM_ABI + void __relocate_upward_and_insert(pointer __from_s, pointer __from_e, _Up&& __elt); void __move_assign(vector& __c, true_type) _NOEXCEPT_(is_nothrow_move_assignable::value); void __move_assign(vector& __c, false_type) @@ -897,13 +916,16 @@ void vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer& __v) { + using __move_via_memcpy = __vector_relocate_trivial_move<_Tp, _Allocator>; + using __destroy_via_noop = __vector_relocate_trivial_destroy<_Tp, _Allocator>; __annotate_delete(); - _VSTD::__construct_backward_with_exception_guarantees(this->__alloc(), this->__begin_, this->__end_, __v.__begin_); + _VSTD::__construct_backward_with_exception_guarantees(this->__alloc(), this->__begin_, this->__end_, __v.__begin_, __move_via_memcpy()); _VSTD::swap(this->__begin_, __v.__begin_); _VSTD::swap(this->__end_, __v.__end_); _VSTD::swap(this->__end_cap(), __v.__end_cap()); __v.__first_ = __v.__begin_; + __v.__destruct_at_end(__v.__begin_, __destroy_via_noop()); __annotate_new(size()); __invalidate_all_iterators(); } @@ -912,14 +934,18 @@ typename vector<_Tp, _Allocator>::pointer vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer& __v, pointer __p) { + using __move_via_memcpy = __vector_relocate_trivial_move<_Tp, _Allocator>; + using __destroy_via_noop = __vector_relocate_trivial_destroy<_Tp, _Allocator>; + __annotate_delete(); pointer __r = __v.__begin_; - _VSTD::__construct_backward_with_exception_guarantees(this->__alloc(), this->__begin_, __p, __v.__begin_); - _VSTD::__construct_forward_with_exception_guarantees(this->__alloc(), __p, this->__end_, __v.__end_); + _VSTD::__construct_backward_with_exception_guarantees(this->__alloc(), this->__begin_, __p, __v.__begin_, __move_via_memcpy()); + _VSTD::__construct_forward_with_exception_guarantees(this->__alloc(), __p, this->__end_, __v.__end_, __move_via_memcpy()); _VSTD::swap(this->__begin_, __v.__begin_); _VSTD::swap(this->__end_, __v.__end_); _VSTD::swap(this->__end_cap(), __v.__end_cap()); __v.__first_ = __v.__begin_; + __v.__destruct_at_end(__v.__begin_, __destroy_via_noop()); __annotate_new(size()); __invalidate_all_iterators(); return __r; @@ -1618,14 +1644,23 @@ typename vector<_Tp, _Allocator>::iterator vector<_Tp, _Allocator>::erase(const_iterator __position) { - _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(_VSTD::addressof(__position)) == this, + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(std::addressof(__position)) == this, "vector::erase(iterator) called with an iterator not referring to this vector"); _LIBCPP_ASSERT(__position != end(), "vector::erase(iterator) called with a non-dereferenceable iterator"); difference_type __ps = __position - cbegin(); - pointer __p = this->__begin_ + __ps; - this->__destruct_at_end(_VSTD::move(__p + 1, this->__end_, __p)); - this->__invalidate_iterators_past(__p-1); + pointer __p = __begin_ + __ps; + if (__vector_trivial_relocate<_Tp, _Allocator>::value) { + _Tp *__rawp = std::__to_address(__p); + __alloc_traits::destroy(__alloc(), __rawp); + --__end_; + if (__p != __end_) { + std::memmove(__rawp, __rawp + 1, (__end_ - __p) * sizeof(*__rawp)); + } + } else { + __destruct_at_end(std::move(__p + 1, __end_, __p)); + } + __invalidate_iterators_past(__p-1); iterator __r = __make_iter(__p); return __r; } @@ -1649,6 +1684,33 @@ return __r; } +// TODO: also be true for nothrow construct functions. +template +using __is_nothrow_constructible_alloc = _BoolConstant<_And< + is_nothrow_constructible<_Tp, _Up>, + _Or< + __is_default_allocator<_Allocator>, + _Not<__has_construct<_Allocator, _Tp*, _Tp> > + > +>::value>; + +template +template +void +vector<_Tp, _Allocator>::__relocate_upward_and_insert(pointer __from_s, pointer __from_e, _Up&& __elt) +{ + if (__vector_trivial_relocate<_Tp, _Allocator>::value && __is_nothrow_constructible_alloc<_Allocator, _Tp, _Up&&>::value) { + std::memmove(std::__to_address(__from_s + 1), std::__to_address(__from_s), (__from_e - __from_s) * sizeof(_Tp)); + ++this->__end_; + __alloc_traits::construct(this->__alloc(), + std::__to_address(__from_s), + std::forward<_Up>(__elt)); + } else { + __move_range(__from_s, __from_e, __from_s + 1); + *__from_s = std::forward<_Up>(__elt); + } +} + template void vector<_Tp, _Allocator>::__move_range(pointer __from_s, pointer __from_e, pointer __to) @@ -1683,11 +1745,10 @@ } else { - __move_range(__p, this->__end_, __p + 1); const_pointer __xr = pointer_traits::pointer_to(__x); if (__p <= __xr && __xr < this->__end_) ++__xr; - *__p = *__xr; + __relocate_upward_and_insert(__p, this->__end_, *__xr); } } else @@ -1717,8 +1778,7 @@ } else { - __move_range(__p, this->__end_, __p + 1); - *__p = _VSTD::move(__x); + __relocate_upward_and_insert(__p, this->__end_, _VSTD::move(__x)); } } else @@ -1748,8 +1808,7 @@ else { __temp_value __tmp(this->__alloc(), _VSTD::forward<_Args>(__args)...); - __move_range(__p, this->__end_, __p + 1); - *__p = _VSTD::move(__tmp.get()); + __relocate_upward_and_insert(__p, this->__end_, _VSTD::move(__tmp.get())); } } else diff --git a/libcxx/test/libcxx/containers/sequences/vector/insert_trivially_relocatable.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/insert_trivially_relocatable.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/containers/sequences/vector/insert_trivially_relocatable.pass.cpp @@ -0,0 +1,91 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// + +// void insert(const_iterator it, const value_type& x); + +#include +#include +#include +#include "test_macros.h" + +template +struct MaybeTriviallyRelocatable {}; +template <> +struct MaybeTriviallyRelocatable { + ~MaybeTriviallyRelocatable() {} +}; + +// This class is trivial for calls, and therefore __trivially_relocatable, iff TR. +// on compilers +template +struct MyClass : MaybeTriviallyRelocatable { + int* assignments; + int value; + explicit MyClass(int* assign_counter, int i) : assignments(assign_counter), value(i) {} + MyClass(const MyClass& rhs) { + assignments = rhs.assignments; + value = rhs.value; + } + MyClass& operator=(const MyClass& rhs) { + ++*assignments; + value = rhs.value; + return *this; + } +#if TEST_STD_VER >= 11 + MyClass(MyClass&& rhs) noexcept { + assignments = rhs.assignments; + value = rhs.value; + } + MyClass& operator=(MyClass&& rhs) { + ++*assignments; + value = rhs.value; + return *this; + } +#endif +}; + +static_assert(!std::__libcpp_is_trivially_relocatable >::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable >::value == + __has_keyword(__is_trivially_relocatable), + ""); + +template +void test() { + int assignments; + using T = MyClass; + const bool trivially_relocatable = std::__libcpp_is_trivially_relocatable::value; + std::vector vec; + vec.reserve(5); + vec.push_back(T(&assignments, 1)); + vec.push_back(T(&assignments, 2)); + vec.push_back(T(&assignments, 3)); + + assignments = 0; + vec.insert(vec.begin() + 2, T(&assignments, 4)); + if (trivially_relocatable) { + assert(assignments == 0); // relocate upward without assigning + } else { + assert(assignments >= 1); + } + + assignments = 0; + vec.insert(vec.begin() + 2, T(&assignments, 5)); + if (trivially_relocatable) { + assert(assignments == 0); // relocate upward without assigning + } else { + assert(assignments >= 1); + } +} + +int main(int, char**) { + test(); + test(); + return 0; +} diff --git a/libcxx/test/libcxx/utilities/meta/meta.unary.prop/is_trivially_relocatable.compile.pass.cpp b/libcxx/test/libcxx/utilities/meta/meta.unary.prop/is_trivially_relocatable.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/meta/meta.unary.prop/is_trivially_relocatable.compile.pass.cpp @@ -0,0 +1,85 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// + +// __libcpp_is_trivially_relocatable + +#include +#include +#include "test_macros.h" + +class Empty {}; + +class Polymorphic { +public: + virtual ~Polymorphic(); +}; + +union Union {}; + +struct bit_zero { + int : 0; +}; + +class Abstract { +public: + virtual ~Abstract() = 0; +}; + +struct NontrivialCopyOnly { + NontrivialCopyOnly(const NontrivialCopyOnly&); +}; + +struct TrivialCopyOnly { + TrivialCopyOnly(const TrivialCopyOnly&) = default; +}; + +#if TEST_STD_VER >= 11 + +struct NontrivialMoveOnly { + NontrivialMoveOnly(NontrivialMoveOnly&&); +}; + +struct TrivialMoveOnly { + TrivialMoveOnly(TrivialMoveOnly&&) = default; +}; +#endif + +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); + +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); + +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); + +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); + +#if TEST_STD_VER >= 11 +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +#endif diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_exception_safety.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_exception_safety.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_exception_safety.pass.cpp @@ -0,0 +1,138 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-exceptions + +// + +// void insert(const_iterator it, const value_type& x); + +#include +#include +#include "test_macros.h" + +static int countdown = 0; +static std::size_t constructions = 0; +static std::size_t destructions = 0; + +struct MyError {}; + +template +struct MaybeTriviallyRelocatable {}; +template <> +struct MaybeTriviallyRelocatable { + ~MaybeTriviallyRelocatable() {} +}; + +template +struct __attribute__((trivial_abi)) MyClass : MaybeTriviallyRelocatable { + int value; + explicit MyClass(int i) : value(i) { ++constructions; } + MyClass(const MyClass& rhs) _NOEXCEPT_(!CC) { + value = rhs.value; + countdown_if(); + ++constructions; + } + MyClass& operator=(const MyClass& rhs) _NOEXCEPT_(!CA) { + value = rhs.value; + countdown_if(); + return *this; + } +#if TEST_STD_VER >= 11 + MyClass(MyClass&& rhs) _NOEXCEPT_(!MC) { + value = rhs.value; + countdown_if(); + ++constructions; + } + MyClass& operator=(MyClass&& rhs) _NOEXCEPT_(!MA) { + value = rhs.value; + countdown_if(); + return *this; + } +#endif + ~MyClass() { ++destructions; } + template + void countdown_if() { + if (C && countdown-- == 0) + throw MyError(); + } +}; + +static_assert(!std::__libcpp_is_trivially_relocatable >::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable >::value == + __has_keyword(__is_trivially_relocatable), + ""); + +// Constructs a vector that throws an exception after `n` non-noexcept +// copy/move construct/assign operations. +// +// (Which operations are noexcept depends on the mask bits.) +// +// We try every value of n from 0 until it no longer throws, which means we +// test every single exceptional control flow path for constructors and +// assignment. +template +void test() { + using T = MyClass; + for (int n = 0;; ++n) { + { + std::vector vec; + vec.reserve(5); + try { + countdown = n; + vec.push_back(T(1)); + vec.push_back(T(2)); + vec.push_back(T(3)); + vec.insert(vec.begin() + 2, T(4)); + vec.insert(vec.begin() + 2, T(5)); + // if we reach here, no exception was thrown + break; + } catch (const MyError&) { + assert(constructions == destructions + vec.size()); + } + } + // destroy the vector and check the invariant again + assert(constructions == destructions); + } +} + +int main(int, char**) { + test<0x00>(); + test<0x01>(); + test<0x02>(); + test<0x03>(); + test<0x04>(); + test<0x05>(); + test<0x06>(); + test<0x07>(); + test<0x08>(); + test<0x09>(); + test<0x0a>(); + test<0x0b>(); + test<0x0c>(); + test<0x0d>(); + test<0x0e>(); + test<0x0f>(); + test<0x10>(); + test<0x11>(); + test<0x12>(); + test<0x13>(); + test<0x14>(); + test<0x15>(); + test<0x16>(); + test<0x17>(); + test<0x18>(); + test<0x19>(); + test<0x1a>(); + test<0x1b>(); + test<0x1c>(); + test<0x1d>(); + test<0x1e>(); + test<0x1f>(); + return 0; +}