8 changes: 8 additions & 0 deletions libcxx/include/__memory/shared_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <__memory/auto_ptr.h>
#include <__memory/compressed_pair.h>
#include <__memory/construct_at.h>
#include <__memory/destroy.h>
#include <__memory/pointer_traits.h>
#include <__memory/shared_count.h>
#include <__memory/uninitialized_algorithms.h>
Expand Down Expand Up @@ -315,7 +316,11 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS shared_ptr {

// A shared_ptr contains only two raw pointers which point to the heap and move constructing already doesn't require
// any bookkeeping, so it's always trivially relocatable.
//
// However, it's not replaceable because of self-assignment, which must prevent the refcount from
// hitting 0.
using __trivially_relocatable = shared_ptr;
using __replaceable = void;

private:
element_type* __ptr_;
Expand Down Expand Up @@ -1210,7 +1215,10 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS weak_ptr {

// A weak_ptr contains only two raw pointers which point to the heap and move constructing already doesn't require
// any bookkeeping, so it's always trivially relocatable.
//
// However, it's not replaceable because we must preserve a non-zero refcount through self-assignment.
using __trivially_relocatable = weak_ptr;
using __replaceable = void;

private:
element_type* __ptr_;
Expand Down
18 changes: 18 additions & 0 deletions libcxx/include/__memory/temp_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@

_LIBCPP_BEGIN_NAMESPACE_STD

template <class _Tp>
struct __temporary_emplace_value {
union {
_Tp __value_;
};

template <class _Allocator, class... _Args>
_LIBCPP_HIDE_FROM_ABI
_LIBCPP_CONSTEXPR_SINCE_CXX20 explicit __temporary_emplace_value(_Allocator& __alloc, _Args&&... __args) {
allocator_traits<_Allocator>::construct(__alloc, std::addressof(__value_), std::forward<_Args>(__args)...);
}

// Don't destroy anything, since we assume that the value gets relocated by whoever uses this type
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 ~__temporary_emplace_value() {}

_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp* __address() { return std::addressof(__value_); }
};

template <class _Tp, class _Alloc>
struct __temp_value {
typedef allocator_traits<_Alloc> _Traits;
Expand Down
30 changes: 5 additions & 25 deletions libcxx/include/__memory/uninitialized_algorithms.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
#include <__iterator/iterator_traits.h>
#include <__iterator/reverse_iterator.h>
#include <__memory/addressof.h>
#include <__memory/destroy.h>
#include <__memory/allocator_traits.h>
#include <__memory/construct_at.h>
#include <__memory/is_trivially_allocator_relocatable.h>
#include <__memory/pointer_traits.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/extent.h>
Expand Down Expand Up @@ -511,14 +513,6 @@ __uninitialized_allocator_value_construct_n_multidimensional(_Alloc& __alloc, _B

#endif // _LIBCPP_STD_VER >= 17

// Destroy all elements in [__first, __last) from left to right using allocator destruction.
template <class _Alloc, class _Iter, class _Sent>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void
__allocator_destroy(_Alloc& __alloc, _Iter __first, _Sent __last) {
for (; __first != __last; ++__first)
allocator_traits<_Alloc>::destroy(__alloc, std::__to_address(__first));
}

template <class _Alloc, class _Iter>
class _AllocatorDestroyRangeReverse {
public:
Expand Down Expand Up @@ -591,19 +585,7 @@ __uninitialized_allocator_copy(_Alloc& __alloc, _Iter1 __first1, _Sent1 __last1,
return std::__rewrap_iter(__first2, __result);
}

template <class _Alloc, class _Type>
struct __allocator_has_trivial_move_construct : _Not<__has_construct<_Alloc, _Type*, _Type&&> > {};

template <class _Type>
struct __allocator_has_trivial_move_construct<allocator<_Type>, _Type> : true_type {};

template <class _Alloc, class _Tp>
struct __allocator_has_trivial_destroy : _Not<__has_destroy<_Alloc, _Tp*> > {};

template <class _Tp, class _Up>
struct __allocator_has_trivial_destroy<allocator<_Tp>, _Up> : true_type {};

// __uninitialized_allocator_relocate relocates the objects in [__first, __last) into __result.
// __uninitialized_allocator_relocate_for_vector 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 equivalent to a memcpy.
//
Expand All @@ -616,15 +598,13 @@ struct __allocator_has_trivial_destroy<allocator<_Tp>, _Up> : true_type {};
// - is_copy_constructible<_ValueType>
// - __libcpp_is_trivially_relocatable<_ValueType>
template <class _Alloc, class _ContiguousIterator>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void __uninitialized_allocator_relocate(
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void __uninitialized_allocator_relocate_for_vector(
_Alloc& __alloc, _ContiguousIterator __first, _ContiguousIterator __last, _ContiguousIterator __result) {
static_assert(__libcpp_is_contiguous_iterator<_ContiguousIterator>::value, "");
using _ValueType = typename iterator_traits<_ContiguousIterator>::value_type;
static_assert(__is_cpp17_move_insertable<_Alloc>::value,
"The specified type does not meet the requirements of Cpp17MoveInsertable");
if (__libcpp_is_constant_evaluated() || !__libcpp_is_trivially_relocatable<_ValueType>::value ||
!__allocator_has_trivial_move_construct<_Alloc, _ValueType>::value ||
!__allocator_has_trivial_destroy<_Alloc, _ValueType>::value) {
if (__libcpp_is_constant_evaluated() || !__is_trivially_allocator_relocatable<_Alloc, _ValueType>::value) {
auto __destruct_first = __result;
auto __guard = std::__make_exception_guard(
_AllocatorDestroyRangeReverse<_Alloc, _ContiguousIterator>(__alloc, __destruct_first, __result));
Expand Down
215 changes: 215 additions & 0 deletions libcxx/include/__memory/uninitialized_relocate.h
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
5 changes: 5 additions & 0 deletions libcxx/include/__memory/unique_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <__type_traits/is_function.h>
#include <__type_traits/is_pointer.h>
#include <__type_traits/is_reference.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_relocatable.h>
Expand Down Expand Up @@ -157,6 +158,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<deleter_type>::value,
unique_ptr,
void>;
using __replaceable =
__conditional_t<__is_replaceable<pointer>::value && __is_replaceable<deleter_type>::value, unique_ptr, void>;

private:
_LIBCPP_COMPRESSED_PAIR(pointer, __ptr_, deleter_type, __deleter_);
Expand Down Expand Up @@ -423,6 +426,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<deleter_type>::value,
unique_ptr,
void>;
using __replaceable =
__conditional_t<__is_replaceable<pointer>::value && __is_replaceable<deleter_type>::value, unique_ptr, void>;

private:
template <class _Up, class _OtherDeleter>
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/__pstl/backends/libdispatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <__iterator/move_iterator.h>
#include <__memory/allocator.h>
#include <__memory/construct_at.h>
#include <__memory/destroy.h>
#include <__memory/unique_ptr.h>
#include <__numeric/reduce.h>
#include <__pstl/backend_fwd.h>
Expand Down
5 changes: 5 additions & 0 deletions libcxx/include/__split_buffer
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <__type_traits/integral_constant.h>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_destructible.h>
#include <__type_traits/is_trivially_relocatable.h>
Expand Down Expand Up @@ -74,6 +75,10 @@ public:
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
__split_buffer,
void>;
using __replaceable =
__conditional_t<__is_replaceable<pointer>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
__split_buffer,
void>;

pointer __first_;
pointer __begin_;
Expand Down
59 changes: 59 additions & 0 deletions libcxx/include/__type_traits/is_replaceable.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
2 changes: 2 additions & 0 deletions libcxx/include/__utility/pair.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <__type_traits/is_implicitly_default_constructible.h>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_relocatable.h>
Expand Down Expand Up @@ -75,6 +76,7 @@ struct _LIBCPP_TEMPLATE_VIS pair
__conditional_t<__libcpp_is_trivially_relocatable<_T1>::value && __libcpp_is_trivially_relocatable<_T2>::value,
pair,
void>;
using __replaceable = __conditional_t<__is_replaceable<_T1>::value && __is_replaceable<_T2>::value, pair, void>;

_LIBCPP_HIDE_FROM_ABI pair(pair const&) = default;
_LIBCPP_HIDE_FROM_ABI pair(pair&&) = default;
Expand Down
202 changes: 110 additions & 92 deletions libcxx/include/__vector/vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@
#include <__memory/allocator.h>
#include <__memory/allocator_traits.h>
#include <__memory/compressed_pair.h>
#include <__memory/destroy.h>
#include <__memory/is_trivially_allocator_relocatable.h>
#include <__memory/noexcept_move_assign_container.h>
#include <__memory/pointer_traits.h>
#include <__memory/swap_allocator.h>
#include <__memory/temp_value.h>
#include <__memory/uninitialized_algorithms.h>
#include <__memory/uninitialized_relocate.h>
#include <__ranges/access.h>
#include <__ranges/concepts.h>
#include <__ranges/container_compatible_range.h>
Expand All @@ -51,6 +54,7 @@
#include <__type_traits/is_constructible.h>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_trivially_relocatable.h>
#include <__type_traits/type_identity.h>
Expand Down Expand Up @@ -118,6 +122,10 @@ class _LIBCPP_TEMPLATE_VIS vector {
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
vector,
void>;
using __replaceable =
__conditional_t<__is_replaceable<pointer>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
vector,
void>;

static_assert(__check_valid_allocator<allocator_type>::value, "");
static_assert(is_same<typename allocator_type::value_type, value_type>::value,
Expand Down Expand Up @@ -467,11 +475,68 @@ class _LIBCPP_TEMPLATE_VIS vector {
this->__destruct_at_end(this->__end_ - 1);
}

_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __position, const_reference __x);
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __position, const_reference __x) {
return emplace(std::move(__position), __x);
}

_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __position, value_type&& __x);
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __position, value_type&& __x) {
return emplace(std::move(__position), std::move(__x));
}
template <class... _Args>
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator emplace(const_iterator __position, _Args&&... __args);
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator emplace(const_iterator __cposition, _Args&&... __args) {
iterator __position = begin() + (__cposition - cbegin());
if (__end_ < __cap_) {
if (__position == end()) {
allocator_traits<_Allocator>::construct(
__alloc_, std::__to_address(__position), std::forward<_Args>(__args)...);
++__end_;
} else {
// Construct a temporary value on the stack, so that in case this throws we haven't modified
// the vector yet. This also takes care of the corner case where we'd be trying to insert
// from an element located in the vector itself, in which case we'd otherwise have to be
// careful about reference invalidation if we didn't make a temporary value.
__temporary_emplace_value<value_type> __tmp(__alloc_, std::forward<_Args>(__args)...);
auto __guard = std::__make_exception_guard([&] {
std::allocator_traits<_Allocator>::destroy(__alloc_, __tmp.__address());
});

// Open up a gap inside the vector by relocating everything to the right and insert the new
// element into the right spot.
//
// If this fails, the relocation operation guarantees that the whole tail of the vector has
// been destroyed. So we set the "new end" of the vector accordingly, which means we basically
// erased the whole tail of the vector. This provides the basic exception guarantee.
try {
std::__uninitialized_allocator_relocate_backward(__alloc_, __position, end(), __position + 1);
} catch (...) {
__end_ = __to_pointer(__position);
throw;
}

// Relocate the temporary value into its final location. If that throws, we know from __allocator_relocate_at
// that the temporary will have been destroyed, but we must still clear the tail of the vector, since otherwise
// we'd leave a gap in the middle of the vector.
__guard.__complete(); // we know the temporary value gets destroyed by the relocation no matter what happens
try {
std::__allocator_relocate_at(__alloc_, __tmp.__address(), std::__to_address(__position));
} catch (...) {
std::__allocator_destroy(__alloc_, __position + 1, end() + 1);
__end_ = __to_pointer(__position);
throw;
}

++__end_;
}
__annotate_increase(1);
return __position;
} else {
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), __position - begin(), __alloc_);
__v.emplace_back(std::forward<_Args>(__args)...);
pointer __p = __to_pointer(__position);
__p = __swap_out_circular_buffer(__v, __p);
return __make_iter(__p);
}
}

_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator
insert(const_iterator __position, size_type __n, const_reference __x);
Expand Down Expand Up @@ -515,8 +580,40 @@ class _LIBCPP_TEMPLATE_VIS vector {
}
#endif

_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __position);
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __first, const_iterator __last);
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __position) {
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
__position != end(), "vector::erase(iterator) called with a non-dereferenceable iterator");
return erase(__position, __position + 1);
}
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __cfirst, const_iterator __clast) {
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__cfirst <= __clast, "vector::erase(first, last) called with invalid range");

iterator __first = begin() + std::distance(cbegin(), __cfirst);
iterator __last = begin() + std::distance(cbegin(), __clast);
if (__first == __last)
return __last;

auto const __n = std::distance(__first, __last);

// If the value_type is nothrow relocatable, we destroy the range being erased and we relocate the tail
// of the vector into the created gap. This is especially efficient if the elements are trivially relocatable.
// Otherwise, we use the standard technique with move-assignments.
//
// Note that unlike vector::insert, we can't use relocation when it is potentially throwing, because
// vector::erase is required not to throw an exception unless T's assignment operator throws. So we
// can bypass the assignment with a relocation, but only when that definitely doesn't throw.
if constexpr (__is_nothrow_allocator_relocatable<_Allocator, value_type>::value) {
std::__allocator_destroy(__alloc_, __first, __last);
std::__uninitialized_allocator_relocate(__alloc_, __last, end(), __first);
} else {
auto __new_end = std::move(__last, end(), __first);
std::__allocator_destroy(__alloc_, __new_end, end());
}

__end_ -= __n;
__annotate_shrink(size() + __n);
return __first;
}

_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void clear() _NOEXCEPT {
size_type __old_size = size();
Expand Down Expand Up @@ -657,6 +754,11 @@ class _LIBCPP_TEMPLATE_VIS vector {
__annotate_shrink(__old_size);
}

_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer __to_pointer(iterator __it) const {
auto __index = __it - begin();
return this->__begin_ + __index;
}

template <class... _Args>
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI inline pointer __emplace_back_slow_path(_Args&&... __args);

Expand Down Expand Up @@ -806,7 +908,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void
vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer<value_type, allocator_type&>& __v) {
__annotate_delete();
auto __new_begin = __v.__begin_ - (__end_ - __begin_);
std::__uninitialized_allocator_relocate(
std::__uninitialized_allocator_relocate_for_vector(
this->__alloc_, std::__to_address(__begin_), std::__to_address(__end_), std::__to_address(__new_begin));
__v.__begin_ = __new_begin;
__end_ = __begin_; // All the objects have been destroyed by relocating them.
Expand All @@ -829,13 +931,13 @@ vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer<value_type, a

// Relocate [__p, __end_) first to avoid having a hole in [__begin_, __end_)
// in case something in [__begin_, __p) throws.
std::__uninitialized_allocator_relocate(
std::__uninitialized_allocator_relocate_for_vector(
this->__alloc_, std::__to_address(__p), std::__to_address(__end_), std::__to_address(__v.__end_));
__v.__end_ += (__end_ - __p);
__end_ = __p; // The objects in [__p, __end_) have been destroyed by relocating them.
auto __new_begin = __v.__begin_ - (__p - __begin_);

std::__uninitialized_allocator_relocate(
std::__uninitialized_allocator_relocate_for_vector(
this->__alloc_, std::__to_address(__begin_), std::__to_address(__p), std::__to_address(__new_begin));
__v.__begin_ = __new_begin;
__end_ = __begin_; // All the objects have been destroyed by relocating them.
Expand Down Expand Up @@ -1110,28 +1212,6 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 inline
#endif
}

template <class _Tp, class _Allocator>
_LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI typename vector<_Tp, _Allocator>::iterator
vector<_Tp, _Allocator>::erase(const_iterator __position) {
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
__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(std::move(__p + 1, this->__end_, __p));
return __make_iter(__p);
}

template <class _Tp, class _Allocator>
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) {
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "vector::erase(first, last) called with invalid range");
pointer __p = this->__begin_ + (__first - begin());
if (__first != __last) {
this->__destruct_at_end(std::move(__p + (__last - __first), this->__end_, __p));
}
return __make_iter(__p);
}

template <class _Tp, class _Allocator>
_LIBCPP_CONSTEXPR_SINCE_CXX20 void
vector<_Tp, _Allocator>::__move_range(pointer __from_s, pointer __from_e, pointer __to) {
Expand All @@ -1147,68 +1227,6 @@ vector<_Tp, _Allocator>::__move_range(pointer __from_s, pointer __from_e, pointe
std::move_backward(__from_s, __from_s + __n, __old_last);
}

template <class _Tp, class _Allocator>
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
vector<_Tp, _Allocator>::insert(const_iterator __position, const_reference __x) {
pointer __p = this->__begin_ + (__position - begin());
if (this->__end_ < this->__cap_) {
if (__p == this->__end_) {
__construct_one_at_end(__x);
} else {
__move_range(__p, this->__end_, __p + 1);
const_pointer __xr = pointer_traits<const_pointer>::pointer_to(__x);
if (std::__is_pointer_in_range(std::__to_address(__p), std::__to_address(__end_), std::addressof(__x)))
++__xr;
*__p = *__xr;
}
} else {
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), __p - this->__begin_, this->__alloc_);
__v.emplace_back(__x);
__p = __swap_out_circular_buffer(__v, __p);
}
return __make_iter(__p);
}

template <class _Tp, class _Allocator>
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
vector<_Tp, _Allocator>::insert(const_iterator __position, value_type&& __x) {
pointer __p = this->__begin_ + (__position - begin());
if (this->__end_ < this->__cap_) {
if (__p == this->__end_) {
__construct_one_at_end(std::move(__x));
} else {
__move_range(__p, this->__end_, __p + 1);
*__p = std::move(__x);
}
} else {
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), __p - this->__begin_, this->__alloc_);
__v.emplace_back(std::move(__x));
__p = __swap_out_circular_buffer(__v, __p);
}
return __make_iter(__p);
}

template <class _Tp, class _Allocator>
template <class... _Args>
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) {
pointer __p = this->__begin_ + (__position - begin());
if (this->__end_ < this->__cap_) {
if (__p == this->__end_) {
__construct_one_at_end(std::forward<_Args>(__args)...);
} else {
__temp_value<value_type, _Allocator> __tmp(this->__alloc_, std::forward<_Args>(__args)...);
__move_range(__p, this->__end_, __p + 1);
*__p = std::move(__tmp.get());
}
} else {
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), __p - this->__begin_, this->__alloc_);
__v.emplace_back(std::forward<_Args>(__args)...);
__p = __swap_out_circular_buffer(__v, __p);
}
return __make_iter(__p);
}

template <class _Tp, class _Allocator>
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
vector<_Tp, _Allocator>::insert(const_iterator __position, size_type __n, const_reference __x) {
Expand Down
2 changes: 2 additions & 0 deletions libcxx/include/array
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ template <size_t I, class T, size_t N> const T&& get(const array<T, N>&&) noexce
#include <__type_traits/is_const.h>
#include <__type_traits/is_constructible.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_relocatable.h>
Expand Down Expand Up @@ -171,6 +172,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD
template <class _Tp, size_t _Size>
struct _LIBCPP_TEMPLATE_VIS array {
using __trivially_relocatable = __conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, array, void>;
using __replaceable = __conditional_t<__is_replaceable<_Tp>::value, array, void>;

// types:
using __self = array;
Expand Down
5 changes: 5 additions & 0 deletions libcxx/include/deque
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ template <class T, class Allocator, class Predicate>
#include <__type_traits/is_convertible.h>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_relocatable.h>
Expand Down Expand Up @@ -526,6 +527,10 @@ public:
__libcpp_is_trivially_relocatable<__map>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
deque,
void>;
using __replaceable =
__conditional_t<__is_replaceable<__map>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
deque,
void>;

static_assert(is_nothrow_default_constructible<allocator_type>::value ==
is_nothrow_default_constructible<__pointer_allocator>::value,
Expand Down
10 changes: 10 additions & 0 deletions libcxx/include/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ module std_core [system] {
header "__type_traits/is_referenceable.h"
export std_core.type_traits.integral_constant
}
module is_replaceable {
header "__type_traits/is_replaceable.h"
export std_core.type_traits.integral_constant
}
module is_same {
header "__type_traits/is_same.h"
export std_core.type_traits.integral_constant
Expand Down Expand Up @@ -1531,9 +1535,11 @@ module std [system] {
module compressed_pair { header "__memory/compressed_pair.h" }
module concepts { header "__memory/concepts.h" }
module construct_at { header "__memory/construct_at.h" }
module destroy { header "__memory/destroy.h" }
module destruct_n { header "__memory/destruct_n.h" }
module fwd { header "__fwd/memory.h" }
module inout_ptr { header "__memory/inout_ptr.h" }
module is_trivially_allocator_relocatable { header "__memory/is_trivially_allocator_relocatable.h" }
module noexcept_move_assign_container { header "__memory/noexcept_move_assign_container.h" }
module out_ptr { header "__memory/out_ptr.h" }
module pointer_traits { header "__memory/pointer_traits.h" }
Expand All @@ -1543,6 +1549,7 @@ module std [system] {
export std.algorithm.in_out_result
}
module raw_storage_iterator { header "__memory/raw_storage_iterator.h" }
module relocate_at { header "__memory/relocate_at.h" }
module shared_count { header "__memory/shared_count.h" }
module shared_ptr { header "__memory/shared_ptr.h" }
module swap_allocator { header "__memory/swap_allocator.h" }
Expand All @@ -1555,6 +1562,9 @@ module std [system] {
header "__memory/uninitialized_algorithms.h"
export std.utility.pair
}
module uninitialized_relocate {
header "__memory/uninitialized_relocate.h"
}
module unique_ptr {
header "__memory/unique_ptr.h"
}
Expand Down
2 changes: 2 additions & 0 deletions libcxx/include/optional
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ namespace std {
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_object.h>
#include <__type_traits/is_reference.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_scalar.h>
#include <__type_traits/is_swappable.h>
Expand Down Expand Up @@ -587,6 +588,7 @@ public:
using value_type = _Tp;

using __trivially_relocatable = conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, optional, void>;
using __replaceable = conditional_t<__is_replaceable<_Tp>::value, optional, void>;

private:
// Disable the reference extension using this static assert.
Expand Down
5 changes: 5 additions & 0 deletions libcxx/include/string
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ basic_string<char32_t> operator""s( const char32_t *str, size_t len );
#include <__type_traits/is_convertible.h>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_standard_layout.h>
#include <__type_traits/is_trivial.h>
Expand Down Expand Up @@ -800,6 +801,10 @@ public:
basic_string,
void>;
#endif
using __replaceable =
__conditional_t<__is_replaceable<pointer>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
basic_string,
void>;

#if _LIBCPP_HAS_ASAN && _LIBCPP_INSTRUMENTED_WITH_ASAN
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 pointer __asan_volatile_wrapper(pointer const& __ptr) const {
Expand Down
2 changes: 2 additions & 0 deletions libcxx/include/tuple
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ template <class... Types>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_reference.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_relocatable.h>
Expand Down Expand Up @@ -550,6 +551,7 @@ class _LIBCPP_TEMPLATE_VIS tuple {

public:
using __trivially_relocatable = __conditional_t<_And<__libcpp_is_trivially_relocatable<_Tp>...>::value, tuple, void>;
using __replaceable = __conditional_t<_And<__is_replaceable<_Tp>...>::value, tuple, void>;

// [tuple.cnstr]

Expand Down
2 changes: 2 additions & 0 deletions libcxx/include/variant
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ namespace std {
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_reference.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_assignable.h>
Expand Down Expand Up @@ -1173,6 +1174,7 @@ class _LIBCPP_TEMPLATE_VIS _LIBCPP_DECLSPEC_EMPTY_BASES variant
public:
using __trivially_relocatable =
conditional_t<_And<__libcpp_is_trivially_relocatable<_Types>...>::value, variant, void>;
using __replaceable = conditional_t<_And<__is_replaceable<_Types>...>::value, variant, void>;

template <bool _Dummy = true,
enable_if_t<__dependent_type<is_default_constructible<__first_type>, _Dummy>::value, int> = 0>
Expand Down
183 changes: 183 additions & 0 deletions libcxx/test/libcxx/memory/uninitialized_allocator_relocate.pass.cpp
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;
}
269 changes: 269 additions & 0 deletions libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp
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);
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,6 @@ struct Throws {
bool Throws::sThrows = false;
#endif

struct Tracker {
int copy_assignments = 0;
int move_assignments = 0;
};

struct TrackedAssignment {
Tracker* tracker_;
TEST_CONSTEXPR_CXX14 explicit TrackedAssignment(Tracker* tracker) : tracker_(tracker) {}

TrackedAssignment(TrackedAssignment const&) = default;
TrackedAssignment(TrackedAssignment&&) = default;

TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment const&) {
tracker_->copy_assignments++;
return *this;
}
TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment&&) {
tracker_->move_assignments++;
return *this;
}
};

struct NonTriviallyRelocatable {
int value_;
TEST_CONSTEXPR NonTriviallyRelocatable() : value_(0) {}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,16 @@ int main(int, char**)
#endif
return 0;
}

// TODO:
// Add emplace test like
//
// std::vector<int> v = {1, 2, 3, 4};
// v.reserve(10000);
// v.emplace(v.begin() + 1, 999);

// TODO:
// Add tests for emplacing and inserting from an element in the vector itself

// TODO:
// There is a bug in emplace, we should be using __uninitialized_allocator_relocate_backward. Also why is this not caught by the tests?

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -107,31 +107,5 @@ int main(int, char**) {
}
#endif

// Make sure we satisfy the complexity requirement in terms of the number of times the assignment
// operator is called.
//
// There is currently ambiguity as to whether this is truly mandated by the Standard, so we only
// test it for libc++.
#ifdef _LIBCPP_VERSION
{
Tracker tracker;
std::vector<TrackedAssignment> v;

// Set up the vector with 5 elements.
for (int i = 0; i != 5; ++i) {
v.emplace_back(&tracker);
}
assert(tracker.copy_assignments == 0);
assert(tracker.move_assignments == 0);

// Erase element [1] from it. Elements [2] [3] [4] should be shifted, so we should
// see 3 move assignments (and nothing else).
v.erase(v.begin() + 1);
assert(v.size() == 4);
assert(tracker.copy_assignments == 0);
assert(tracker.move_assignments == 3);
}
#endif

return 0;
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -196,31 +196,5 @@ int main(int, char**) {
assert(it == v.begin() + 2);
}

// Make sure we satisfy the complexity requirement in terms of the number of times the assignment
// operator is called.
//
// There is currently ambiguity as to whether this is truly mandated by the Standard, so we only
// test it for libc++.
#ifdef _LIBCPP_VERSION
{
Tracker tracker;
std::vector<TrackedAssignment> v;

// Set up the vector with 5 elements.
for (int i = 0; i != 5; ++i) {
v.emplace_back(&tracker);
}
assert(tracker.copy_assignments == 0);
assert(tracker.move_assignments == 0);

// Erase elements [1] and [2] from it. Elements [3] [4] should be shifted, so we should
// see 2 move assignments (and nothing else).
v.erase(v.begin() + 1, v.begin() + 3);
assert(v.size() == 3);
assert(tracker.copy_assignments == 0);
assert(tracker.move_assignments == 2);
}
#endif

return 0;
}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.