| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef _LIBCPP___MEMORY_UNINITIALIZED_RELOCATE_H | ||
| #define _LIBCPP___MEMORY_UNINITIALIZED_RELOCATE_H | ||
|
|
||
| #include <__config> | ||
| #include <__iterator/iterator_traits.h> | ||
| #include <__memory/addressof.h> | ||
| #include <__memory/allocator_traits.h> | ||
| #include <__memory/destroy.h> | ||
| #include <__memory/is_trivially_allocator_relocatable.h> | ||
| #include <__memory/pointer_traits.h> | ||
| #include <__memory/relocate_at.h> | ||
| #include <__type_traits/is_constant_evaluated.h> | ||
| #include <__type_traits/is_nothrow_constructible.h> | ||
| #include <__type_traits/is_trivially_relocatable.h> | ||
| #include <__utility/is_pointer_in_range.h> | ||
| #include <__utility/move.h> | ||
|
|
||
| #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) | ||
| # pragma GCC system_header | ||
| #endif | ||
|
|
||
| _LIBCPP_PUSH_MACROS | ||
| #include <__undef_macros> | ||
|
|
||
| _LIBCPP_BEGIN_NAMESPACE_STD | ||
|
|
||
| // __uninitialized_relocate relocates the objects in [__first, __last) into __result. | ||
| // | ||
| // Relocation means that the objects in [__first, __last) are placed into __result as-if by move-construct and destroy, | ||
| // except that the move constructor and destructor may never be called if they are known to be trivially relocatable. | ||
| // | ||
| // This algorithm works even if part of the resulting range overlaps with [__first, __last), as long as __result itself | ||
| // is not in [__first, last). | ||
| // | ||
| // If an exception is thrown, all the elements in the input range and in the output range are destroyed. | ||
| // | ||
| // Preconditions: | ||
| // - __result doesn't contain any objects and [__first, __last) contains objects | ||
| // - __result is not in [__first, __last) | ||
| // Postconditions: | ||
| // - __result contains the objects from [__first, __last) | ||
| // - [__first, __last) doesn't contain any objects | ||
| template <class _InputIter, class _NothrowForwardIter> | ||
| _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _NothrowForwardIter | ||
| __uninitialized_relocate(_InputIter __first, _InputIter __last, _NothrowForwardIter __result) { | ||
| if constexpr (__libcpp_is_contiguous_iterator<_InputIter>::value && | ||
| __libcpp_is_contiguous_iterator<_NothrowForwardIter>::value) { | ||
| _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES( | ||
| !std::__is_pointer_in_range(std::__to_address(__first), std::__to_address(__last), std::__to_address(__result)), | ||
| "uninitialized_relocate requires the start of the result not to overlap with the input range"); | ||
| } | ||
| using _ValueType = typename iterator_traits<_InputIter>::value_type; | ||
|
|
||
| // If we have contiguous iterators over a trivially relocatable type, use the builtin that is | ||
| // roughly equivalent to memmove. | ||
| if constexpr (__libcpp_is_contiguous_iterator<_InputIter>::value && | ||
| __libcpp_is_contiguous_iterator<_NothrowForwardIter>::value && | ||
| __libcpp_is_trivially_relocatable<_ValueType>::value) { | ||
| if (!__libcpp_is_constant_evaluated()) { | ||
| // TODO: We might be able to memcpy if we don't overlap at all? | ||
| std::__libcpp_builtin_trivially_relocate_at( | ||
| std::__to_address(__first), std::__to_address(__last), std::__to_address(__result)); | ||
| return __result + (__last - __first); | ||
| } | ||
| } | ||
|
|
||
| // Otherwise, relocate elements one by one. | ||
| // | ||
| // If this throws an exception, we destroy the rest of the range we were relocating, and | ||
| // we also destroy everything we had relocated up to now. | ||
| auto const __first_result = __result; | ||
| try { | ||
| while (__first != __last) { | ||
| std::__relocate_at(std::addressof(*__first), std::addressof(*__result)); | ||
| ++__first; | ||
| ++__result; | ||
| } | ||
| } catch (...) { | ||
| std::destroy(++__first, __last); | ||
| std::destroy(__first_result, __result); | ||
| throw; | ||
| } | ||
| return __result; | ||
| } | ||
|
|
||
| // __uninitialized_relocate_backward is like __uninitialized_relocate, but it relocates the elements in | ||
| // the range [first, last) to another range ending at __result_last. The elements are relocated in reverse | ||
| // order, but their relative order is preserved. | ||
| template <class _BidirectionalIter, class _NothrowBidirectionalIter> | ||
| _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _NothrowBidirectionalIter __uninitialized_relocate_backward( | ||
| _BidirectionalIter __first, _BidirectionalIter __last, _NothrowBidirectionalIter __result_last) { | ||
| if constexpr (__libcpp_is_contiguous_iterator<_BidirectionalIter>::value && | ||
| __libcpp_is_contiguous_iterator<_NothrowBidirectionalIter>::value) { | ||
| _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES( | ||
| !std::__is_pointer_in_range( | ||
| std::__to_address(__first), std::__to_address(__last), std::__to_address(__result_last) - 1), | ||
| "uninitialized_relocate_backward requires the end of the result not to overlap with the input range"); | ||
| } | ||
| using _ValueType = typename iterator_traits<_BidirectionalIter>::value_type; | ||
|
|
||
| // If we have contiguous iterators over a trivially relocatable type, use the builtin that is | ||
| // roughly equivalent to memmove. | ||
| if constexpr (__libcpp_is_contiguous_iterator<_BidirectionalIter>::value && | ||
| __libcpp_is_contiguous_iterator<_NothrowBidirectionalIter>::value && | ||
| __libcpp_is_trivially_relocatable<_ValueType>::value) { | ||
| if (!__libcpp_is_constant_evaluated()) { | ||
| auto __result = __result_last - (__last - __first); | ||
| // TODO: We might be able to memcpy if we don't overlap at all? | ||
| std::__libcpp_builtin_trivially_relocate_at( | ||
| std::__to_address(__first), std::__to_address(__last), std::__to_address(__result)); | ||
| return __result; | ||
| } | ||
| } | ||
|
|
||
| // Otherwise, relocate elements one by one, starting from the end. | ||
| // | ||
| // If this throws an exception, we destroy the rest of the range we were relocating, and | ||
| // we also destroy everything we had relocated up to now. | ||
| auto __result = __result_last; | ||
| try { | ||
| while (__last != __first) { | ||
| --__last; | ||
| --__result; | ||
| std::__relocate_at(std::addressof(*__last), std::addressof(*__result)); | ||
| } | ||
| } catch (...) { | ||
| std::destroy(__first, __last); | ||
| std::destroy(__result, __result_last); | ||
| throw; | ||
| } | ||
| return __result; | ||
| } | ||
|
|
||
| // Equivalent to __uninitialized_relocate, but uses the provided allocator's construct() and destroy() methods. | ||
| template <class _Alloc, class _InputIter, class _NothrowForwardIter> | ||
| _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _NothrowForwardIter __uninitialized_allocator_relocate( | ||
| _Alloc& __alloc, _InputIter __first, _InputIter __last, _NothrowForwardIter __result) { | ||
| if constexpr (__libcpp_is_contiguous_iterator<_InputIter>::value && | ||
| __libcpp_is_contiguous_iterator<_NothrowForwardIter>::value) { | ||
| _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES( | ||
| !std::__is_pointer_in_range(std::__to_address(__first), std::__to_address(__last), std::__to_address(__last)), | ||
| "uninitialized_allocator_relocate requires the result not to overlap with the input range"); | ||
| } | ||
|
|
||
| using _ValueType = typename iterator_traits<_InputIter>::value_type; | ||
| if (__allocator_has_trivial_move_construct<_Alloc, _ValueType>::value && | ||
| __allocator_has_trivial_destroy<_Alloc, _ValueType>::value) { | ||
| (void)__alloc; // ignore the allocator | ||
| return std::__uninitialized_relocate(std::move(__first), std::move(__last), std::move(__result)); | ||
| } else { | ||
| auto const __first_result = __result; | ||
| try { | ||
| while (__first != __last) { | ||
| std::__allocator_relocate_at(__alloc, std::addressof(*__first), std::addressof(*__result)); | ||
| ++__first; | ||
| ++__result; | ||
| } | ||
| } catch (...) { | ||
| std::__allocator_destroy(__alloc, ++__first, __last); | ||
| std::__allocator_destroy(__alloc, __first_result, __result); | ||
| throw; | ||
| } | ||
| return __result; | ||
| } | ||
| } | ||
|
|
||
| // Equivalent to __uninitialized_relocate_backward, but uses the provided allocator's construct() and destroy() methods. | ||
| template <class _Alloc, class _BidirectionalIter, class _NothrowBidirectionalIter> | ||
| _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _NothrowBidirectionalIter | ||
| __uninitialized_allocator_relocate_backward( | ||
| _Alloc& __alloc, _BidirectionalIter __first, _BidirectionalIter __last, _NothrowBidirectionalIter __result_last) { | ||
| if constexpr (__libcpp_is_contiguous_iterator<_BidirectionalIter>::value && | ||
| __libcpp_is_contiguous_iterator<_NothrowBidirectionalIter>::value) { | ||
| _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES( | ||
| !std::__is_pointer_in_range( | ||
| std::__to_address(__first), std::__to_address(__last), std::__to_address(__result_last) - 1), | ||
| "uninitialized_allocator_relocate_backward requires the end of the result not to overlap with the input range"); | ||
| } | ||
|
|
||
| using _ValueType = typename iterator_traits<_BidirectionalIter>::value_type; | ||
| if (__allocator_has_trivial_move_construct<_Alloc, _ValueType>::value && | ||
| __allocator_has_trivial_destroy<_Alloc, _ValueType>::value) { | ||
| (void)__alloc; // ignore the allocator | ||
| return std::__uninitialized_relocate_backward(std::move(__first), std::move(__last), std::move(__result_last)); | ||
| } else { | ||
| auto __result = __result_last; | ||
| try { | ||
| while (__last != __first) { | ||
| --__last; | ||
| --__result; | ||
| std::__allocator_relocate_at(__alloc, std::addressof(*__last), std::addressof(*__result)); | ||
| } | ||
| } catch (...) { | ||
| std::__allocator_destroy(__alloc, __first, __last); | ||
| std::__allocator_destroy(__alloc, __result, __result_last); | ||
| throw; | ||
| } | ||
|
|
||
| return __result; | ||
| } | ||
| } | ||
|
|
||
| _LIBCPP_END_NAMESPACE_STD | ||
|
|
||
| _LIBCPP_POP_MACROS | ||
|
|
||
| #endif // _LIBCPP___MEMORY_UNINITIALIZED_RELOCATE_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef _LIBCPP___TYPE_TRAITS_IS_REPLACEABLE_H | ||
| #define _LIBCPP___TYPE_TRAITS_IS_REPLACEABLE_H | ||
|
|
||
| #include <__config> | ||
| #include <__type_traits/enable_if.h> | ||
| #include <__type_traits/integral_constant.h> | ||
| #include <__type_traits/is_same.h> | ||
| #include <__type_traits/is_trivially_copyable.h> | ||
|
|
||
| #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) | ||
| # pragma GCC system_header | ||
| #endif | ||
|
|
||
| _LIBCPP_BEGIN_NAMESPACE_STD | ||
|
|
||
| // A type is replaceable if `x = std::move(y)` is equivalent to: | ||
| // | ||
| // std::destroy_at(&x) | ||
| // std::construct_at(&x, std::move(y)) | ||
| // | ||
| // This allows turning a move-assignment into a sequence of destroy + move-construct, which | ||
| // is often more efficient. This is especially relevant when the move-construct is in fact | ||
| // part of a trivial relocation from somewhere else, in which case there is a huge win. | ||
| // | ||
| // Note that this requires language support in order to be really effective, but we | ||
| // currently emulate the base template with something very conservative. | ||
| template <class _Tp, class = void> | ||
| struct __is_replaceable : is_trivially_copyable<_Tp> {}; | ||
|
|
||
| template <class _Tp> | ||
| struct __is_replaceable<_Tp, __enable_if_t<is_same<_Tp, typename _Tp::__replaceable>::value> > : true_type {}; | ||
|
|
||
| // Determines whether an allocator member of a container is replaceable. | ||
| // | ||
| // We take into account whether the allocator is propagated on assignments. If the allocator | ||
| // always compares equal, then it doesn't matter whether we propagate it or not on assignments, | ||
| // the result will be the same and we can just as much move-construct it instead. | ||
| // | ||
| // If the allocator does not always compare equal, we check whether it propagates on assignment | ||
| // and it is replaceable. | ||
| template <class _AllocatorTraits> | ||
| struct __container_allocator_is_replaceable | ||
| : integral_constant<bool, | ||
| _AllocatorTraits::is_always_equal::value || | ||
| (_AllocatorTraits::propagate_on_container_move_assignment::value && | ||
| _AllocatorTraits::propagate_on_container_copy_assignment::value && | ||
| __is_replaceable<typename _AllocatorTraits::allocator_type>::value)> {}; | ||
|
|
||
| _LIBCPP_END_NAMESPACE_STD | ||
|
|
||
| #endif // _LIBCPP___TYPE_TRAITS_IS_REPLACEABLE_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| // Test the std::__uninitialized_allocator_relocate internal algorithm. | ||
|
|
||
| #include <__memory/uninitialized_relocate.h> | ||
| #include <cassert> | ||
| #include <cstddef> | ||
| #include <memory> | ||
|
|
||
| #include "test_macros.h" | ||
| #include "test_iterators.h" | ||
| #include "min_allocator.h" | ||
|
|
||
| template <class Allocator> | ||
| struct Fixture { | ||
| using Traits = std::allocator_traits<Allocator>; | ||
| using ValueType = typename Traits::value_type; | ||
| using Pointer = typename Traits::pointer; | ||
|
|
||
| constexpr Fixture(std::size_t n) : size(n) { | ||
| source = allocator.allocate(n); | ||
| for (std::size_t i = 0; i != n; ++i) { | ||
| Traits::construct(allocator, std::to_address(source + i), ValueType(i)); | ||
| } | ||
|
|
||
| dest = allocator.allocate(n); | ||
| } | ||
|
|
||
| constexpr void relocated(std::size_t n) { relocated_ = n; } | ||
|
|
||
| constexpr ~Fixture() { | ||
| for (std::size_t i = 0; i != relocated_; ++i) { | ||
| Traits::destroy(allocator, std::to_address(dest + i)); | ||
| } | ||
| allocator.deallocate(dest, size); | ||
|
|
||
| for (std::size_t i = relocated_; i != size; ++i) { | ||
| Traits::destroy(allocator, std::to_address(source + i)); | ||
| } | ||
| allocator.deallocate(source, size); | ||
| } | ||
|
|
||
| Allocator allocator; | ||
| std::size_t size; | ||
| std::size_t relocated_ = 0; | ||
| Pointer source; | ||
| Pointer dest; | ||
| }; | ||
|
|
||
| template <class Alloc, class Iterator, class OutputIterator> | ||
| constexpr void test() { | ||
| using T = std::allocator_traits<Alloc>::value_type; | ||
| using Pointer = std::allocator_traits<Alloc>::pointer; | ||
|
|
||
| // Relocate an empty range | ||
| { | ||
| Fixture<Alloc> t(10); | ||
|
|
||
| OutputIterator res = std::__uninitialized_allocator_relocate( | ||
| t.allocator, | ||
| Iterator(std::to_address(t.source)), | ||
| Iterator(std::to_address(t.source)), | ||
| OutputIterator(std::to_address(t.dest))); | ||
| assert(res == OutputIterator(std::to_address(t.dest))); | ||
| t.relocated(0); | ||
|
|
||
| for (int i = 0; i != 10; ++i) { | ||
| assert(t.source[i] == T(i)); | ||
| } | ||
| } | ||
|
|
||
| // Range of size 1 | ||
| { | ||
| Fixture<Alloc> t(10); | ||
|
|
||
| OutputIterator res = std::__uninitialized_allocator_relocate( | ||
| t.allocator, | ||
| Iterator(std::to_address(t.source)), | ||
| Iterator(std::to_address(t.source + 1)), | ||
| OutputIterator(std::to_address(t.dest))); | ||
| assert(res == OutputIterator(std::to_address(t.dest + 1))); | ||
| t.relocated(1); | ||
|
|
||
| assert(t.dest[0] == T(0)); | ||
| assert(t.source[1] == T(1)); | ||
| assert(t.source[2] == T(2)); | ||
| // ... | ||
| } | ||
|
|
||
| // Range of normal size | ||
| { | ||
| Fixture<Alloc> t(10); | ||
|
|
||
| OutputIterator res = std::__uninitialized_allocator_relocate( | ||
| t.allocator, | ||
| Iterator(std::to_address(t.source)), | ||
| Iterator(std::to_address(t.source + 10)), | ||
| OutputIterator(std::to_address(t.dest))); | ||
| assert(res == OutputIterator(std::to_address(t.dest + 10))); | ||
| t.relocated(10); | ||
|
|
||
| for (int i = 0; i != 10; ++i) { | ||
| assert(t.dest[i] == T(i)); | ||
| } | ||
| } | ||
|
|
||
| // Relocate with some overlap between the input and the output range | ||
| { | ||
| Alloc allocator; | ||
| Pointer buff = allocator.allocate(10); | ||
| for (std::size_t i = 5; i != 10; ++i) { | ||
| std::allocator_traits<Alloc>::construct(allocator, std::to_address(buff + i), T(i)); | ||
| } | ||
|
|
||
| OutputIterator res = std::__uninitialized_allocator_relocate( | ||
| allocator, | ||
| Iterator(std::to_address(buff + 5)), | ||
| Iterator(std::to_address(buff + 10)), | ||
| OutputIterator(std::to_address(buff))); | ||
| assert(res == OutputIterator(std::to_address(buff + 5))); | ||
|
|
||
| for (int i = 0; i != 5; ++i) { | ||
| assert(buff[i] == T(i + 5)); | ||
| std::allocator_traits<Alloc>::destroy(allocator, std::to_address(buff + i)); | ||
| } | ||
| allocator.deallocate(buff, 10); | ||
| } | ||
|
|
||
| // TODO: Add exception test | ||
| } | ||
|
|
||
| struct NotTriviallyRelocatable { | ||
| constexpr explicit NotTriviallyRelocatable(int i) : value_(i) {} | ||
| constexpr NotTriviallyRelocatable(NotTriviallyRelocatable&& other) : value_(other.value_) { other.value_ = -1; } | ||
| constexpr friend bool operator==(NotTriviallyRelocatable const& a, NotTriviallyRelocatable const& b) { | ||
| return a.value_ == b.value_; | ||
| } | ||
|
|
||
| int value_; | ||
| }; | ||
|
|
||
| template <class T> | ||
| struct ConsructAllocator : std::allocator<T> { | ||
| template < class... Args> | ||
| constexpr void construct(T* loc, Args&&... args) { | ||
| std::construct_at(loc, std::forward<Args>(args)...); | ||
| } | ||
| }; | ||
|
|
||
| template <class T> | ||
| struct DestroyAllocator : std::allocator<T> { | ||
| constexpr void destroy(T* loc) { std::destroy_at(loc); } | ||
| }; | ||
|
|
||
| constexpr bool tests() { | ||
| test<std::allocator<int>, cpp17_input_iterator<int*>, forward_iterator<int*>>(); | ||
| test<std::allocator<int>, contiguous_iterator<int*>, contiguous_iterator<int*>>(); | ||
| test<min_allocator<int>, cpp17_input_iterator<int*>, forward_iterator<int*>>(); | ||
| test<std::allocator<NotTriviallyRelocatable>, | ||
| cpp17_input_iterator<NotTriviallyRelocatable*>, | ||
| forward_iterator<NotTriviallyRelocatable*>>(); | ||
| test<ConsructAllocator<NotTriviallyRelocatable>, | ||
| cpp17_input_iterator<NotTriviallyRelocatable*>, | ||
| forward_iterator<NotTriviallyRelocatable*>>(); | ||
| test<DestroyAllocator<NotTriviallyRelocatable>, | ||
| cpp17_input_iterator<NotTriviallyRelocatable*>, | ||
| forward_iterator<NotTriviallyRelocatable*>>(); | ||
| return true; | ||
| } | ||
|
|
||
| int main(int, char**) { | ||
| tests(); | ||
| #if TEST_STD_VER >= 20 | ||
| static_assert(tests(), ""); | ||
| #endif | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,269 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include <__type_traits/is_replaceable.h> | ||
| #include <array> | ||
| #include <deque> | ||
| #include <exception> | ||
| #include <expected> | ||
| #include <memory> | ||
| #include <optional> | ||
| #include <string> | ||
| #include <tuple> | ||
| #include <type_traits> | ||
| #include <variant> | ||
| #include <vector> | ||
|
|
||
| #include "constexpr_char_traits.h" | ||
| #include "test_allocator.h" | ||
| #include "test_macros.h" | ||
|
|
||
| #ifndef TEST_HAS_NO_LOCALIZATION | ||
| # include <locale> | ||
| #endif | ||
|
|
||
| template <class T> | ||
| struct NonPropagatingStatefulMoveAssignAlloc : std::allocator<T> { | ||
| using propagate_on_container_move_assignment = std::false_type; | ||
| using is_always_equal = std::false_type; | ||
| }; | ||
|
|
||
| template <class T> | ||
| struct NonPropagatingStatefulCopyAssignAlloc : std::allocator<T> { | ||
| using propagate_on_container_copy_assignment = std::false_type; | ||
| using is_always_equal = std::false_type; | ||
| }; | ||
|
|
||
| template <class T> | ||
| struct NonPropagatingStatelessMoveAssignAlloc : std::allocator<T> { | ||
| using propagate_on_container_move_assignment = std::false_type; | ||
| using is_always_equal = std::true_type; | ||
| }; | ||
|
|
||
| template <class T> | ||
| struct NonPropagatingStatelessCopyAssignAlloc : std::allocator<T> { | ||
| using propagate_on_container_copy_assignment = std::false_type; | ||
| using is_always_equal = std::true_type; | ||
| }; | ||
|
|
||
| struct Empty {}; | ||
| static_assert(std::__is_replaceable<char>::value, ""); | ||
| static_assert(std::__is_replaceable<int>::value, ""); | ||
| static_assert(std::__is_replaceable<double>::value, ""); | ||
| static_assert(std::__is_replaceable<Empty>::value, ""); | ||
|
|
||
| struct TriviallyCopyable { | ||
| char c; | ||
| int i; | ||
| Empty s; | ||
| }; | ||
| static_assert(std::__is_replaceable<TriviallyCopyable>::value, ""); | ||
|
|
||
| struct NotTriviallyCopyable { | ||
| NotTriviallyCopyable(const NotTriviallyCopyable&); | ||
| ~NotTriviallyCopyable(); | ||
| }; | ||
| static_assert(!std::__is_replaceable<NotTriviallyCopyable>::value, ""); | ||
|
|
||
| struct MoveOnlyTriviallyCopyable { | ||
| MoveOnlyTriviallyCopyable(const MoveOnlyTriviallyCopyable&) = delete; | ||
| MoveOnlyTriviallyCopyable& operator=(const MoveOnlyTriviallyCopyable&) = delete; | ||
| MoveOnlyTriviallyCopyable(MoveOnlyTriviallyCopyable&&) = default; | ||
| MoveOnlyTriviallyCopyable& operator=(MoveOnlyTriviallyCopyable&&) = default; | ||
| }; | ||
| static_assert(std::__is_replaceable<MoveOnlyTriviallyCopyable>::value, ""); | ||
|
|
||
| struct CustomCopyAssignment { | ||
| CustomCopyAssignment(const CustomCopyAssignment&) = default; | ||
| CustomCopyAssignment(CustomCopyAssignment&&) = default; | ||
| CustomCopyAssignment& operator=(const CustomCopyAssignment&); | ||
| CustomCopyAssignment& operator=(CustomCopyAssignment&&) = default; | ||
| }; | ||
| static_assert(!std::__is_replaceable<CustomCopyAssignment>::value, ""); | ||
|
|
||
| struct CustomMoveAssignment { | ||
| CustomMoveAssignment(const CustomMoveAssignment&) = default; | ||
| CustomMoveAssignment(CustomMoveAssignment&&) = default; | ||
| CustomMoveAssignment& operator=(const CustomMoveAssignment&) = default; | ||
| CustomMoveAssignment& operator=(CustomMoveAssignment&&); | ||
| }; | ||
| static_assert(!std::__is_replaceable<CustomMoveAssignment>::value, ""); | ||
|
|
||
| // library-internal types | ||
| // ---------------------- | ||
|
|
||
| // __split_buffer | ||
| static_assert(std::__is_replaceable<std::__split_buffer<int> >::value, ""); | ||
| static_assert(std::__is_replaceable<std::__split_buffer<NotTriviallyCopyable> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::__split_buffer<int, NonPropagatingStatefulCopyAssignAlloc<int> > >::value, | ||
| ""); | ||
| static_assert(!std::__is_replaceable<std::__split_buffer<int, NonPropagatingStatefulMoveAssignAlloc<int> > >::value, | ||
| ""); | ||
| static_assert(std::__is_replaceable<std::__split_buffer<int, NonPropagatingStatelessCopyAssignAlloc<int> > >::value, | ||
| ""); | ||
| static_assert(std::__is_replaceable<std::__split_buffer<int, NonPropagatingStatelessMoveAssignAlloc<int> > >::value, | ||
| ""); | ||
|
|
||
| // standard library types | ||
| // ---------------------- | ||
|
|
||
| // array | ||
| static_assert(std::__is_replaceable<std::array<int, 0> >::value, ""); | ||
| static_assert(std::__is_replaceable<std::array<NotTriviallyCopyable, 0> >::value, ""); | ||
| static_assert(std::__is_replaceable<std::array<std::unique_ptr<int>, 0> >::value, ""); | ||
|
|
||
| static_assert(std::__is_replaceable<std::array<int, 1> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::array<NotTriviallyCopyable, 1> >::value, ""); | ||
| static_assert(std::__is_replaceable<std::array<std::unique_ptr<int>, 1> >::value, ""); | ||
|
|
||
| // basic_string | ||
| struct MyChar { | ||
| char c; | ||
| }; | ||
| template <class T> | ||
| struct NotReplaceableCharTraits : constexpr_char_traits<T> { | ||
| NotReplaceableCharTraits(const NotReplaceableCharTraits&); | ||
| NotReplaceableCharTraits& operator=(const NotReplaceableCharTraits&); | ||
| ~NotReplaceableCharTraits(); | ||
| }; | ||
|
|
||
| static_assert(std::__is_replaceable<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >::value, | ||
| ""); | ||
| static_assert( | ||
| std::__is_replaceable<std::basic_string<char, NotReplaceableCharTraits<char>, std::allocator<char> > >::value, ""); | ||
| static_assert( | ||
| std::__is_replaceable<std::basic_string<MyChar, constexpr_char_traits<MyChar>, std::allocator<MyChar> > >::value, | ||
| ""); | ||
| static_assert(!std::__is_replaceable<std::basic_string<char, std::char_traits<char>, test_allocator<char> > >::value, | ||
| ""); | ||
| static_assert(std::__is_replaceable< | ||
| std::basic_string<MyChar, NotReplaceableCharTraits<MyChar>, std::allocator<MyChar> > >::value, | ||
| ""); | ||
| static_assert( | ||
| !std::__is_replaceable< | ||
| std::basic_string<char, std::char_traits<char>, NonPropagatingStatefulCopyAssignAlloc<char> > >::value, | ||
| ""); | ||
| static_assert( | ||
| !std::__is_replaceable< | ||
| std::basic_string<char, std::char_traits<char>, NonPropagatingStatefulMoveAssignAlloc<char> > >::value, | ||
| ""); | ||
| static_assert( | ||
| std::__is_replaceable< | ||
| std::basic_string<char, std::char_traits<char>, NonPropagatingStatelessCopyAssignAlloc<char> > >::value, | ||
| ""); | ||
| static_assert( | ||
| std::__is_replaceable< | ||
| std::basic_string<char, std::char_traits<char>, NonPropagatingStatelessMoveAssignAlloc<char> > >::value, | ||
| ""); | ||
|
|
||
| // deque | ||
| static_assert(std::__is_replaceable<std::deque<int> >::value, ""); | ||
| static_assert(std::__is_replaceable<std::deque<NotTriviallyCopyable> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::deque<int, test_allocator<int> > >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::deque<int, NonPropagatingStatefulCopyAssignAlloc<int> > >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::deque<int, NonPropagatingStatefulMoveAssignAlloc<int> > >::value, ""); | ||
| static_assert(std::__is_replaceable<std::deque<int, NonPropagatingStatelessCopyAssignAlloc<int> > >::value, ""); | ||
| static_assert(std::__is_replaceable<std::deque<int, NonPropagatingStatelessMoveAssignAlloc<int> > >::value, ""); | ||
|
|
||
| // exception_ptr | ||
| static_assert(!std::__is_replaceable<std::exception_ptr>::value, ""); | ||
|
|
||
| // expected | ||
| #if TEST_STD_VER >= 23 | ||
| static_assert(std::__is_replaceable<std::expected<int, int> >::value); | ||
| static_assert(!std::__is_replaceable<std::expected<CustomCopyAssignment, int>>::value); | ||
| static_assert(!std::__is_replaceable<std::expected<int, CustomCopyAssignment>>::value); | ||
| static_assert(!std::__is_replaceable<std::expected<CustomCopyAssignment, CustomCopyAssignment>>::value); | ||
| #endif | ||
|
|
||
| // locale | ||
| #ifndef TEST_HAS_NO_LOCALIZATION | ||
| static_assert(!std::__is_replaceable<std::locale>::value, ""); | ||
| #endif | ||
|
|
||
| // optional | ||
| #if TEST_STD_VER >= 17 | ||
| static_assert(std::__is_replaceable<std::optional<int>>::value, ""); | ||
| static_assert(!std::__is_replaceable<std::optional<CustomCopyAssignment>>::value, ""); | ||
| #endif | ||
|
|
||
| // pair | ||
| static_assert(std::__is_replaceable<std::pair<int, int> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::pair<CustomCopyAssignment, int> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::pair<int, CustomCopyAssignment> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::pair<CustomCopyAssignment, CustomCopyAssignment> >::value, ""); | ||
|
|
||
| // shared_ptr | ||
| static_assert(!std::__is_replaceable<std::shared_ptr<int> >::value, ""); | ||
|
|
||
| // tuple | ||
| #if TEST_STD_VER >= 11 | ||
| static_assert(std::__is_replaceable<std::tuple<> >::value, ""); | ||
|
|
||
| static_assert(std::__is_replaceable<std::tuple<int> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::tuple<CustomCopyAssignment> >::value, ""); | ||
|
|
||
| static_assert(std::__is_replaceable<std::tuple<int, int> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::tuple<CustomCopyAssignment, int> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::tuple<int, CustomCopyAssignment> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::tuple<CustomCopyAssignment, CustomCopyAssignment> >::value, ""); | ||
| #endif // TEST_STD_VER >= 11 | ||
|
|
||
| // unique_ptr | ||
| struct NonReplaceableDeleter { | ||
| NonReplaceableDeleter(const NonReplaceableDeleter&); | ||
| NonReplaceableDeleter& operator=(const NonReplaceableDeleter&); | ||
| ~NonReplaceableDeleter(); | ||
|
|
||
| template <class T> | ||
| void operator()(T*); | ||
| }; | ||
|
|
||
| struct NonReplaceablePointer { | ||
| struct pointer { | ||
| pointer(const pointer&); | ||
| pointer& operator=(const pointer&); | ||
| ~pointer(); | ||
| }; | ||
|
|
||
| template <class T> | ||
| void operator()(T*); | ||
| }; | ||
|
|
||
| static_assert(std::__is_replaceable<std::unique_ptr<int> >::value, ""); | ||
| static_assert(std::__is_replaceable<std::unique_ptr<CustomCopyAssignment> >::value, ""); | ||
| static_assert(std::__is_replaceable<std::unique_ptr<int[]> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::unique_ptr<int, NonReplaceableDeleter> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::unique_ptr<int[], NonReplaceableDeleter> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::unique_ptr<int, NonReplaceablePointer> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::unique_ptr<int[], NonReplaceablePointer> >::value, ""); | ||
|
|
||
| // variant | ||
| #if TEST_STD_VER >= 17 | ||
| static_assert(std::__is_replaceable<std::variant<int> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::variant<CustomCopyAssignment> >::value, ""); | ||
|
|
||
| static_assert(std::__is_replaceable<std::variant<int, int> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::variant<CustomCopyAssignment, int> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::variant<int, CustomCopyAssignment> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::variant<CustomCopyAssignment, CustomCopyAssignment> >::value, ""); | ||
| #endif // TEST_STD_VER >= 17 | ||
|
|
||
| // vector | ||
| static_assert(std::__is_replaceable<std::vector<int> >::value, ""); | ||
| static_assert(std::__is_replaceable<std::vector<CustomCopyAssignment> >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::vector<int, test_allocator<int> > >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::vector<int, NonPropagatingStatefulCopyAssignAlloc<int> > >::value, ""); | ||
| static_assert(!std::__is_replaceable<std::vector<int, NonPropagatingStatefulMoveAssignAlloc<int> > >::value, ""); | ||
| static_assert(std::__is_replaceable<std::vector<int, NonPropagatingStatelessCopyAssignAlloc<int> > >::value, ""); | ||
| static_assert(std::__is_replaceable<std::vector<int, NonPropagatingStatelessMoveAssignAlloc<int> > >::value, ""); | ||
|
|
||
| // weak_ptr | ||
| static_assert(!std::__is_replaceable<std::weak_ptr<CustomCopyAssignment> >::value, ""); | ||
|
|
||
| // TODO: Mark all the replaceable STL types as such |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // 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: c++03 && !stdlib=libc++ | ||
|
|
||
| // <vector> | ||
|
|
||
| // Validate various member functions of std::vector with an ADL-hijacking operator& | ||
|
|
||
| #include <vector> | ||
| #include <utility> | ||
|
|
||
| #include "operator_hijacker.h" | ||
| #include "test_iterators.h" | ||
|
|
||
| using Vector = std::vector<operator_hijacker>; | ||
|
|
||
| void test( | ||
| Vector v, Vector::const_iterator it, cpp17_input_iterator<operator_hijacker*> other_it, operator_hijacker val) { | ||
| // emplace / insert | ||
| v.emplace(it); | ||
| v.insert(it, it, it); | ||
| v.insert(it, other_it, other_it); | ||
| v.insert(it, operator_hijacker()); | ||
| v.insert(it, 1, val); | ||
| v.insert(it, val); | ||
|
|
||
| // erase | ||
| v.erase(it); | ||
| v.erase(it, it); | ||
|
|
||
| // assignment | ||
| v = static_cast<Vector&>(v); | ||
| v = std::move(v); | ||
|
|
||
| // construction | ||
| { Vector v2(std::move(v)); } | ||
| { Vector v2(std::move(v), std::allocator<operator_hijacker>()); } | ||
|
|
||
| // swap | ||
| v.swap(v); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -107,31 +107,5 @@ int main(int, char**) { | |
| } | ||
| #endif | ||
|
|
||
| return 0; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -196,31 +196,5 @@ int main(int, char**) { | |
| assert(it == v.begin() + 2); | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||