[libcxx-commits] [libcxx] [WG21] Draft PR for discussion of the P2687 trivial relocation approach (PR #116714)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Mar 21 07:32:22 PDT 2025
https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/116714
>From 974f620481befbb278b97832eaba249ca65abb20 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 19 Nov 2024 00:01:22 +0100
Subject: [PATCH 1/5] TODO: Improve the tests for insert and emplace
---
.../vector/vector.modifiers/emplace.pass.cpp | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/emplace.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/emplace.pass.cpp
index 667ce483806e7..a596e52e4a52f 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/emplace.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/emplace.pass.cpp
@@ -161,3 +161,22 @@ 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?
+
+// TODO:
+// Merge this test with emplace_extra.pass.cpp?
+
+// TODO:
+// Add a test for iterator invalidation when the vector grows, to catch an incorrect implementation that would construct the new element on the stack and then move it (which fails if the new value throws upon move construction, since we invalidated our iterators and shouldnt).
>From 59f567785bf79059f133acf7a5e0915e185c583d Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 18 Nov 2024 23:08:08 +0100
Subject: [PATCH 2/5] [libc++] Add the __is_replaceable type trait
That type trait represents whether move-assigning an object is
equivalent to destroying it and then move-constructing a new one
from the same argument. This will be useful in a few places where
we may want to destroy + construct instead of doing an assignment,
in particular with relocation.
---
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__exception/exception_ptr.h | 5 +-
libcxx/include/__expected/expected.h | 3 +
libcxx/include/__locale | 5 +-
libcxx/include/__memory/shared_ptr.h | 7 +
libcxx/include/__memory/unique_ptr.h | 5 +
libcxx/include/__split_buffer | 5 +
libcxx/include/__type_traits/is_replaceable.h | 59 ++++
libcxx/include/__utility/pair.h | 3 +
libcxx/include/__vector/vector.h | 5 +
libcxx/include/array | 2 +
libcxx/include/deque | 5 +
libcxx/include/module.modulemap | 4 +
libcxx/include/optional | 2 +
libcxx/include/string | 7 +
libcxx/include/tuple | 2 +
libcxx/include/variant | 14 +-
.../is_replaceable.compile.pass.cpp | 269 ++++++++++++++++++
18 files changed, 395 insertions(+), 8 deletions(-)
create mode 100644 libcxx/include/__type_traits/is_replaceable.h
create mode 100644 libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 255a0474c0f6b..c7601c3d388fa 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -834,6 +834,7 @@ set(files
__type_traits/is_reference.h
__type_traits/is_reference_wrapper.h
__type_traits/is_referenceable.h
+ __type_traits/is_replaceable.h
__type_traits/is_same.h
__type_traits/is_scalar.h
__type_traits/is_signed.h
diff --git a/libcxx/include/__exception/exception_ptr.h b/libcxx/include/__exception/exception_ptr.h
index 6257e6f729bf3..e93971cf7fc48 100644
--- a/libcxx/include/__exception/exception_ptr.h
+++ b/libcxx/include/__exception/exception_ptr.h
@@ -65,8 +65,11 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
friend _LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep) _NOEXCEPT;
public:
- // exception_ptr is basically a COW string.
+ // exception_ptr is basically a COW string so it is trivially relocatable.
+ // However, it's not replaceable because destroying and move-constructing could cause
+ // the underlying refcount to hit 0 if we're self-assigning.
using __trivially_relocatable _LIBCPP_NODEBUG = exception_ptr;
+ using __replaceable _LIBCPP_NODEBUG = void;
_LIBCPP_HIDE_FROM_ABI exception_ptr() _NOEXCEPT : __ptr_() {}
_LIBCPP_HIDE_FROM_ABI exception_ptr(nullptr_t) _NOEXCEPT : __ptr_() {}
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 03bbd1623ed5c..73d2845efa548 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -29,6 +29,7 @@
#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_constructible.h>
@@ -470,6 +471,8 @@ class expected : private __expected_base<_Tp, _Err> {
__conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value && __libcpp_is_trivially_relocatable<_Err>::value,
expected,
void>;
+ using __replaceable _LIBCPP_NODEBUG =
+ __conditional_t<__is_replaceable<_Tp>::value && __is_replaceable<_Err>::value, expected, void>;
template <class _Up>
using rebind = expected<_Up, error_type>;
diff --git a/libcxx/include/__locale b/libcxx/include/__locale
index dfe79d5e506f1..111063f2b7e06 100644
--- a/libcxx/include/__locale
+++ b/libcxx/include/__locale
@@ -49,8 +49,11 @@ _LIBCPP_HIDE_FROM_ABI const _Facet& use_facet(const locale&);
class _LIBCPP_EXPORTED_FROM_ABI locale {
public:
- // locale is essentially a shared_ptr that doesn't support weak_ptrs and never got a move constructor.
+ // locale is essentially a shared_ptr that doesn't support weak_ptrs and never got a move constructor,
+ // so it is trivially relocatable. However, it is not replaceable because self-assignment must prevent
+ // the refcount from hitting 0.
using __trivially_relocatable _LIBCPP_NODEBUG = locale;
+ using __replaceable _LIBCPP_NODEBUG = void;
// types:
class _LIBCPP_EXPORTED_FROM_ABI facet;
diff --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index d513fa692c1f9..ef2572ee24443 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -317,7 +317,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 _LIBCPP_NODEBUG = shared_ptr;
+ using __replaceable _LIBCPP_NODEBUG = void;
private:
element_type* __ptr_;
@@ -1212,7 +1216,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 _LIBCPP_NODEBUG = weak_ptr;
+ using __replaceable _LIBCPP_NODEBUG = void;
private:
element_type* __ptr_;
diff --git a/libcxx/include/__memory/unique_ptr.h b/libcxx/include/__memory/unique_ptr.h
index 29d391fc80fe4..746e028a94cfe 100644
--- a/libcxx/include/__memory/unique_ptr.h
+++ b/libcxx/include/__memory/unique_ptr.h
@@ -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>
@@ -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 _LIBCPP_NODEBUG =
+ __conditional_t<__is_replaceable<pointer>::value && __is_replaceable<deleter_type>::value, unique_ptr, void>;
private:
_LIBCPP_COMPRESSED_PAIR(pointer, __ptr_, deleter_type, __deleter_);
@@ -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 _LIBCPP_NODEBUG =
+ __conditional_t<__is_replaceable<pointer>::value && __is_replaceable<deleter_type>::value, unique_ptr, void>;
private:
template <class _Up, class _OtherDeleter>
diff --git a/libcxx/include/__split_buffer b/libcxx/include/__split_buffer
index a8f679cc30a9c..98229844d43e5 100644
--- a/libcxx/include/__split_buffer
+++ b/libcxx/include/__split_buffer
@@ -28,6 +28,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>
@@ -72,6 +73,10 @@ public:
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
__split_buffer,
void>;
+ using __replaceable _LIBCPP_NODEBUG =
+ __conditional_t<__is_replaceable<pointer>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
+ __split_buffer,
+ void>;
pointer __first_;
pointer __begin_;
diff --git a/libcxx/include/__type_traits/is_replaceable.h b/libcxx/include/__type_traits/is_replaceable.h
new file mode 100644
index 0000000000000..46eb6f79ecb69
--- /dev/null
+++ b/libcxx/include/__type_traits/is_replaceable.h
@@ -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
diff --git a/libcxx/include/__utility/pair.h b/libcxx/include/__utility/pair.h
index 7689ab2a48c6a..e01736975a68c 100644
--- a/libcxx/include/__utility/pair.h
+++ b/libcxx/include/__utility/pair.h
@@ -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>
@@ -75,6 +76,8 @@ struct _LIBCPP_TEMPLATE_VIS pair
__conditional_t<__libcpp_is_trivially_relocatable<_T1>::value && __libcpp_is_trivially_relocatable<_T2>::value,
pair,
void>;
+ using __replaceable _LIBCPP_NODEBUG =
+ __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;
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index 244506065db61..faa08a00d5973 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -55,6 +55,7 @@
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_pointer.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>
@@ -119,6 +120,10 @@ class _LIBCPP_TEMPLATE_VIS vector {
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
vector,
void>;
+ using __replaceable _LIBCPP_NODEBUG =
+ __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,
diff --git a/libcxx/include/array b/libcxx/include/array
index 1b9bcd6891d95..a76bf9d755323 100644
--- a/libcxx/include/array
+++ b/libcxx/include/array
@@ -134,6 +134,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>
@@ -175,6 +176,7 @@ template <class _Tp, size_t _Size>
struct _LIBCPP_TEMPLATE_VIS array {
using __trivially_relocatable _LIBCPP_NODEBUG =
__conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, array, void>;
+ using __replaceable _LIBCPP_NODEBUG = __conditional_t<__is_replaceable<_Tp>::value, array, void>;
// types:
using __self _LIBCPP_NODEBUG = array;
diff --git a/libcxx/include/deque b/libcxx/include/deque
index 95200b4801d7f..6946a57e445fe 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -230,6 +230,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>
@@ -530,6 +531,10 @@ public:
__libcpp_is_trivially_relocatable<__map>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
deque,
void>;
+ using __replaceable _LIBCPP_NODEBUG =
+ __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,
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index b0720703bd0de..da26ae244df88 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -275,6 +275,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
diff --git a/libcxx/include/optional b/libcxx/include/optional
index c325140ee66f2..5f8f306cae06c 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -210,6 +210,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>
@@ -590,6 +591,7 @@ public:
using __trivially_relocatable _LIBCPP_NODEBUG =
conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, optional, void>;
+ using __replaceable _LIBCPP_NODEBUG = conditional_t<__is_replaceable<_Tp>::value, optional, void>;
private:
// Disable the reference extension using this static assert.
diff --git a/libcxx/include/string b/libcxx/include/string
index b280f5f458459..efb692494a270 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -630,6 +630,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>
@@ -794,11 +795,17 @@ public:
// the memory to avoid triggering false positives.
// Therefore it's crucial to ensure the destructor is called.
using __trivially_relocatable = void;
+ // TODO
# else
using __trivially_relocatable _LIBCPP_NODEBUG = __conditional_t<
__libcpp_is_trivially_relocatable<allocator_type>::value && __libcpp_is_trivially_relocatable<pointer>::value,
basic_string,
void>;
+ using __replaceable _LIBCPP_NODEBUG =
+ __conditional_t<__is_replaceable<pointer>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
+ basic_string,
+ void>;
+
# endif
# if _LIBCPP_HAS_ASAN && _LIBCPP_INSTRUMENTED_WITH_ASAN
diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index 5d968bfd4015a..18b8988dcca00 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -250,6 +250,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>
@@ -555,6 +556,7 @@ class _LIBCPP_TEMPLATE_VIS tuple {
public:
using __trivially_relocatable _LIBCPP_NODEBUG =
__conditional_t<_And<__libcpp_is_trivially_relocatable<_Tp>...>::value, tuple, void>;
+ using __replaceable _LIBCPP_NODEBUG = __conditional_t<_And<__is_replaceable<_Tp>...>::value, tuple, void>;
// [tuple.cnstr]
diff --git a/libcxx/include/variant b/libcxx/include/variant
index 3786d9524020b..e5f7f3df4ef0c 100644
--- a/libcxx/include/variant
+++ b/libcxx/include/variant
@@ -246,6 +246,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>
@@ -851,8 +852,7 @@ _LIBCPP_VARIANT_MOVE_CONSTRUCTOR(
_LIBCPP_VARIANT_MOVE_CONSTRUCTOR(
_Trait::_Available,
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __move_constructor(__move_constructor&& __that) noexcept(
- __all<is_nothrow_move_constructible_v<_Types>...>::value)
- : __move_constructor(__valueless_t{}) {
+ __all<is_nothrow_move_constructible_v<_Types>...>::value) : __move_constructor(__valueless_t{}) {
this->__generic_construct(*this, std::move(__that));
} _LIBCPP_EAT_SEMICOLON);
@@ -888,8 +888,9 @@ _LIBCPP_VARIANT_COPY_CONSTRUCTOR(
_LIBCPP_VARIANT_COPY_CONSTRUCTOR(
_Trait::_Available,
- _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __copy_constructor(const __copy_constructor& __that)
- : __copy_constructor(__valueless_t{}) { this->__generic_construct(*this, __that); } _LIBCPP_EAT_SEMICOLON);
+ _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_CONSTEXPR_SINCE_CXX20 __copy_constructor(const __copy_constructor& __that) : __copy_constructor(
+ __valueless_t{}) { this->__generic_construct(*this, __that); } _LIBCPP_EAT_SEMICOLON);
_LIBCPP_VARIANT_COPY_CONSTRUCTOR(
_Trait::_Unavailable,
@@ -1175,6 +1176,7 @@ class _LIBCPP_TEMPLATE_VIS _LIBCPP_DECLSPEC_EMPTY_BASES _LIBCPP_NO_SPECIALIZATIO
public:
using __trivially_relocatable _LIBCPP_NODEBUG =
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>
@@ -1578,8 +1580,8 @@ visit(_Visitor&& __visitor, _Vs&&... __vs) {
template <class... _Types>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 auto
-swap(variant<_Types...>& __lhs,
- variant<_Types...>& __rhs) noexcept(noexcept(__lhs.swap(__rhs))) -> decltype(__lhs.swap(__rhs)) {
+swap(variant<_Types...>& __lhs, variant<_Types...>& __rhs) noexcept(noexcept(__lhs.swap(__rhs)))
+ -> decltype(__lhs.swap(__rhs)) {
return __lhs.swap(__rhs);
}
diff --git a/libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp b/libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp
new file mode 100644
index 0000000000000..c52f5462f8bb3
--- /dev/null
+++ b/libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp
@@ -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
>From 12859a8569a3b777ce5ba5b7da5f7a46c10f4023 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Sat, 23 Nov 2024 22:02:17 +0100
Subject: [PATCH 3/5] [libc++][WIP] Implement relocation primitives
---
libcxx/include/CMakeLists.txt | 3 +
.../is_trivially_allocator_relocatable.h | 57 +++++
libcxx/include/__memory/relocate_at.h | 88 +++++++
.../__memory/uninitialized_algorithms.h | 21 +-
.../include/__memory/uninitialized_relocate.h | 215 ++++++++++++++++++
libcxx/include/__vector/vector.h | 6 +-
libcxx/include/module.modulemap | 5 +
.../uninitialized_allocator_relocate.pass.cpp | 183 +++++++++++++++
8 files changed, 558 insertions(+), 20 deletions(-)
create mode 100644 libcxx/include/__memory/is_trivially_allocator_relocatable.h
create mode 100644 libcxx/include/__memory/relocate_at.h
create mode 100644 libcxx/include/__memory/uninitialized_relocate.h
create mode 100644 libcxx/test/libcxx/memory/uninitialized_allocator_relocate.pass.cpp
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index c7601c3d388fa..7cfbbf739c408 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -560,6 +560,7 @@ set(files
__memory/destroy.h
__memory/destruct_n.h
__memory/inout_ptr.h
+ __memory/is_trivially_allocator_relocatable.h
__memory/noexcept_move_assign_container.h
__memory/out_ptr.h
__memory/pointer_traits.h
@@ -567,12 +568,14 @@ set(files
__memory/ranges_destroy.h
__memory/ranges_uninitialized_algorithms.h
__memory/raw_storage_iterator.h
+ __memory/relocate_at.h
__memory/shared_count.h
__memory/shared_ptr.h
__memory/swap_allocator.h
__memory/temp_value.h
__memory/temporary_buffer.h
__memory/uninitialized_algorithms.h
+ __memory/uninitialized_relocate.h
__memory/unique_ptr.h
__memory/unique_temporary_buffer.h
__memory/uses_allocator.h
diff --git a/libcxx/include/__memory/is_trivially_allocator_relocatable.h b/libcxx/include/__memory/is_trivially_allocator_relocatable.h
new file mode 100644
index 0000000000000..64563b3d7a92d
--- /dev/null
+++ b/libcxx/include/__memory/is_trivially_allocator_relocatable.h
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_IS_TRIVIALLY_ALLOCATOR_RELOCATABLE_H
+#define _LIBCPP___MEMORY_IS_TRIVIALLY_ALLOCATOR_RELOCATABLE_H
+
+#include <__config>
+#include <__memory/allocator_traits.h>
+#include <__type_traits/conjunction.h>
+#include <__type_traits/disjunction.h>
+#include <__type_traits/integral_constant.h>
+#include <__type_traits/is_nothrow_constructible.h>
+#include <__type_traits/is_trivially_relocatable.h>
+#include <__type_traits/negation.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+// A type is trivially allocator relocatable if the allocator's move construction and destruction
+// don't do anything beyond calling the type's move constructor and destructor, and if the type
+// itself is trivially relocatable.
+
+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 {};
+
+template <class _Alloc, class _Tp>
+struct __is_trivially_allocator_relocatable
+ : integral_constant<bool,
+ __allocator_has_trivial_move_construct<_Alloc, _Tp>::value &&
+ __allocator_has_trivial_destroy<_Alloc, _Tp>::value &&
+ __libcpp_is_trivially_relocatable<_Tp>::value> {};
+
+template <class _Alloc, class _Tp>
+struct __is_nothrow_allocator_relocatable
+ : _Or<_And<__allocator_has_trivial_move_construct<_Alloc, _Tp>, is_nothrow_move_constructible<_Tp>>,
+ __is_trivially_allocator_relocatable<_Alloc, _Tp>> {};
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___MEMORY_IS_TRIVIALLY_ALLOCATOR_RELOCATABLE_H
diff --git a/libcxx/include/__memory/relocate_at.h b/libcxx/include/__memory/relocate_at.h
new file mode 100644
index 0000000000000..257f1bc1c4e5c
--- /dev/null
+++ b/libcxx/include/__memory/relocate_at.h
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_RELOCATE_AT_H
+#define _LIBCPP___MEMORY_RELOCATE_AT_H
+
+#include <__memory/allocator_traits.h>
+#include <__memory/construct_at.h>
+#include <__memory/is_trivially_allocator_relocatable.h>
+#include <__type_traits/enable_if.h>
+#include <__type_traits/is_constant_evaluated.h>
+#include <__type_traits/is_trivially_relocatable.h>
+#include <__utility/move.h>
+#include <__utility/scope_guard.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <class _Tp>
+struct __destroy_object {
+ _LIBCPP_CONSTEXPR_SINCE_CXX20 void operator()() const { std::__destroy_at(__obj_); }
+ _Tp* __obj_;
+};
+
+template <class _Alloc, class _Tp>
+struct __allocator_destroy_object {
+ _LIBCPP_CONSTEXPR_SINCE_CXX20 void operator()() const { allocator_traits<_Alloc>::destroy(__alloc_, __obj_); }
+ _Alloc& __alloc_;
+ _Tp* __obj_;
+};
+
+template <class _Tp>
+_LIBCPP_HIDE_FROM_ABI _Tp* __libcpp_builtin_trivially_relocate_at(_Tp* __source, _Tp* __dest) _NOEXCEPT {
+ static_assert(__libcpp_is_trivially_relocatable<_Tp>::value, "");
+ // Casting to void* to suppress clang complaining that this is technically UB.
+ __builtin_memcpy(static_cast<void*>(__dest), __source, sizeof(_Tp));
+ return __dest;
+}
+
+template <class _Tp>
+_LIBCPP_HIDE_FROM_ABI _Tp* __libcpp_builtin_trivially_relocate_at(_Tp* __first, _Tp* __last, _Tp* __dest) _NOEXCEPT {
+ static_assert(__libcpp_is_trivially_relocatable<_Tp>::value, "");
+ // Casting to void* to suppress clang complaining that this is technically UB.
+ __builtin_memmove(static_cast<void*>(__dest), __first, (__last - __first) * sizeof(_Tp));
+ return __dest;
+}
+
+template <class _Tp>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp* __relocate_at(_Tp* __dest, _Tp* __source) {
+ if constexpr (__libcpp_is_trivially_relocatable<_Tp>::value) {
+ if (!__libcpp_is_constant_evaluated()) {
+ return std::__libcpp_builtin_trivially_relocate_at(__source, __dest);
+ }
+ }
+ auto __guard = std::__make_scope_guard(__destroy_object<_Tp>{__source});
+ return std::__construct_at(__dest, std::move(*__source));
+}
+
+template <class _Alloc, class _Tp>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp*
+__allocator_relocate_at(_Alloc& __alloc, _Tp* __dest, _Tp* __source) {
+ if constexpr (__allocator_has_trivial_move_construct<_Alloc, _Tp>::value &&
+ __allocator_has_trivial_destroy<_Alloc, _Tp>::value) {
+ (void)__alloc; // ignore the allocator
+ return std::__relocate_at(__dest, __source);
+ } else {
+ auto __guard = std::__make_scope_guard(__allocator_destroy_object<_Alloc, _Tp>{__alloc, __source});
+ allocator_traits<_Alloc>::construct(__alloc, __dest, std::move(*__source));
+ return __dest;
+ }
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___MEMORY_RELOCATE_AT_H
diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index d7f210d12a212..e494c04f8e135 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -22,6 +22,7 @@
#include <__memory/allocator_traits.h>
#include <__memory/construct_at.h>
#include <__memory/destroy.h>
+#include <__memory/is_trivially_allocator_relocatable.h>
#include <__memory/pointer_traits.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/extent.h>
@@ -584,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.
//
@@ -609,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));
diff --git a/libcxx/include/__memory/uninitialized_relocate.h b/libcxx/include/__memory/uninitialized_relocate.h
new file mode 100644
index 0000000000000..b7b6b4fba4106
--- /dev/null
+++ b/libcxx/include/__memory/uninitialized_relocate.h
@@ -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(*__result), std::addressof(*__first));
+ ++__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(*__result), std::addressof(*__last));
+ }
+ } 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(*__result), std::addressof(*__first));
+ ++__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(*__result), std::addressof(*__last));
+ }
+ } 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
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index faa08a00d5973..5ecd7d9447859 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -830,7 +830,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.
@@ -853,13 +853,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.
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index da26ae244df88..bd6dbf66cb1b4 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -1575,6 +1575,7 @@ module std [system] {
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" }
@@ -1585,6 +1586,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" }
@@ -1597,6 +1599,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"
}
diff --git a/libcxx/test/libcxx/memory/uninitialized_allocator_relocate.pass.cpp b/libcxx/test/libcxx/memory/uninitialized_allocator_relocate.pass.cpp
new file mode 100644
index 0000000000000..662f2174ba3c3
--- /dev/null
+++ b/libcxx/test/libcxx/memory/uninitialized_allocator_relocate.pass.cpp
@@ -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;
+}
>From 4974e743769bb42ba8ffd8e632d1d1283f69445b Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 14 Nov 2024 13:54:18 +0100
Subject: [PATCH 4/5] [WIP] Implement std::vector operations in terms of
relocation
---
libcxx/docs/ReleaseNotes/21.rst | 3 +
libcxx/include/__memory/temp_value.h | 18 ++
libcxx/include/__vector/vector.h | 282 ++++++++++--------
.../vector/vector.modifiers/common.h | 22 --
.../vector.modifiers/erase_iter.pass.cpp | 26 --
.../vector.modifiers/erase_iter_iter.pass.cpp | 26 --
6 files changed, 173 insertions(+), 204 deletions(-)
diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst
index 2439360797023..07e44de806b33 100644
--- a/libcxx/docs/ReleaseNotes/21.rst
+++ b/libcxx/docs/ReleaseNotes/21.rst
@@ -48,6 +48,9 @@ Improvements and New Features
- Updated formatting library to Unicode 16.0.0.
+- The ``std::vector::erase`` function has been optimized for types that can be relocated trivially (such as ``std::string``),
+ yielding speed ups witnessed to be around 2x for these types (but subject to the use case).
+
Deprecations and Removals
-------------------------
diff --git a/libcxx/include/__memory/temp_value.h b/libcxx/include/__memory/temp_value.h
index 4a133b3fbcf6c..bd23de3aef832 100644
--- a/libcxx/include/__memory/temp_value.h
+++ b/libcxx/include/__memory/temp_value.h
@@ -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;
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index 5ecd7d9447859..5082c35236adc 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -37,11 +37,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>
@@ -455,14 +458,56 @@ class _LIBCPP_TEMPLATE_VIS vector {
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void push_back(value_type&& __x) { emplace_back(std::move(__x)); }
+ // TODO: I don't think this handles self-referential argument properly.
template <class... _Args>
- _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reference __emplace_back(_Args&&... __args) {
+ // Likely: we have enough capacity to append an element without growing.
+ if (__end_ < __cap_) {
+ allocator_traits<_Allocator>::construct(__alloc_, std::__to_address(__end_), std::forward<_Args>(__args)...);
+ ++__end_;
+ __annotate_increase(1);
+ return *(__end_ - 1);
+ }
+
+ // Otherwise, relocate all the elements into a sufficiently large vector, either via
+ // relocation or by copying them if moving the elements might throw (since we need to
+ // provide the strong exception safety guarantee).
+ vector<_Tp, _Allocator> __tmp(__alloc_);
+ size_type const __new_size = __recommend(size() + 1);
+ __tmp.reserve(__new_size);
+ if _LIBCPP_CONSTEXPR (is_nothrow_move_constructible<_Tp>::value) {
+ __tmp.__end_ = std::__uninitialized_allocator_relocate(__alloc_, begin(), end(), __tmp.__begin_);
+ __tmp.__annotate_increase(size());
+ __end_ = __begin_; // *this was relocated out of
+ } else {
+ for (reference __element : *this) {
+ __tmp.__emplace_back(std::move_if_noexcept(__element)); // TODO: verify codegen since this is a recursive call
+ }
+ }
+
+ // At this point, the original contents of the vector are in __tmp, which is large enough.
+ // Construct the new last element.
+ try {
+ __tmp.__emplace_back(std::forward<_Args>(__args)...); // TODO: verify codegen
+ } catch (...) {
+ this->swap(__tmp);
+ throw;
+ }
+
+ this->swap(__tmp);
+ return *(__end_ - 1);
+ }
+
#if _LIBCPP_STD_VER >= 17
- reference
- emplace_back(_Args&&... __args);
+ template <class... _Args>
+ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reference emplace_back(_Args&&... __args) {
+ return __emplace_back(std::forward<_Args>(__args)...);
+ }
#else
- void
- emplace_back(_Args&&... __args);
+ template <class... _Args>
+ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void emplace_back(_Args&&... __args) {
+ __emplace_back(std::forward<_Args>(__args)...);
+ }
#endif
#if _LIBCPP_STD_VER >= 23
@@ -477,11 +522,70 @@ 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) {
+ return emplace(std::move(__position), std::move(__x));
+ }
- _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __position, value_type&& __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());
+
+ // Unlikely, the vector is at capacity and we must grow
+ if (__end_ == __cap_) {
+ __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);
+ }
+
+ if (__position == end()) {
+ allocator_traits<_Allocator>::construct(
+ __alloc_, std::__to_address(__to_pointer(__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(); // the temporary value gets destroyed by the relocation no matter what happens
+ try {
+ std::__allocator_relocate_at(__alloc_, std::__to_address(__to_pointer(__position)), __tmp.__address());
+ } catch (...) {
+ std::__allocator_destroy(__alloc_, __position + 1, end() + 1);
+ __end_ = __to_pointer(__position);
+ throw;
+ }
+
+ ++__end_;
+ }
+ __annotate_increase(1);
+ return __position;
+ }
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator
insert(const_iterator __position, size_type __n, const_reference __x);
@@ -525,8 +629,42 @@ 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. This is
+ // arguably an overspecification in the Standard.
+ if _LIBCPP_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();
@@ -670,8 +808,10 @@ class _LIBCPP_TEMPLATE_VIS vector {
__annotate_shrink(__old_size);
}
- template <class... _Args>
- _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI inline pointer __emplace_back_slow_path(_Args&&... __args);
+ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer __to_pointer(iterator __it) const {
+ auto __index = __it - begin();
+ return this->__begin_ + __index;
+ }
// The following functions are no-ops outside of AddressSanitizer mode.
// We call annotations for every allocator, unless explicitly disabled.
@@ -1109,62 +1249,6 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void vector<_Tp, _Allocator>::shrink_to_fit() _NOE
}
}
-template <class _Tp, class _Allocator>
-template <class... _Args>
-_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::pointer
-vector<_Tp, _Allocator>::__emplace_back_slow_path(_Args&&... __args) {
- __split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), size(), this->__alloc_);
- // __v.emplace_back(std::forward<_Args>(__args)...);
- __alloc_traits::construct(this->__alloc_, std::__to_address(__v.__end_), std::forward<_Args>(__args)...);
- __v.__end_++;
- __swap_out_circular_buffer(__v);
- return this->__end_;
-}
-
-template <class _Tp, class _Allocator>
-template <class... _Args>
-_LIBCPP_CONSTEXPR_SINCE_CXX20 inline
-#if _LIBCPP_STD_VER >= 17
- typename vector<_Tp, _Allocator>::reference
-#else
- void
-#endif
- vector<_Tp, _Allocator>::emplace_back(_Args&&... __args) {
- pointer __end = this->__end_;
- if (__end < this->__cap_) {
- __construct_one_at_end(std::forward<_Args>(__args)...);
- ++__end;
- } else {
- __end = __emplace_back_slow_path(std::forward<_Args>(__args)...);
- }
- this->__end_ = __end;
-#if _LIBCPP_STD_VER >= 17
- return *(__end - 1);
-#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) {
@@ -1180,68 +1264,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) {
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/common.h b/libcxx/test/std/containers/sequences/vector/vector.modifiers/common.h
index 72cd47a50b2c0..24627d9fe5f45 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/common.h
+++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/common.h
@@ -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) {}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp
index 0c65bd1034bb8..11ce5c7cd8f8e 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp
@@ -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;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.pass.cpp
index 104dfb4cb07d4..fc00a720ece2f 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.pass.cpp
@@ -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;
}
>From 0f8646abb2438ff4aadcaeb5fa75d7bdc76a969b Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 10 Feb 2025 11:11:20 +0100
Subject: [PATCH 5/5] WIP: test improvements
---
.../uninitialized_allocator_relocate.pass.cpp | 35 ++++++++++++++++++-
1 file changed, 34 insertions(+), 1 deletion(-)
diff --git a/libcxx/test/libcxx/memory/uninitialized_allocator_relocate.pass.cpp b/libcxx/test/libcxx/memory/uninitialized_allocator_relocate.pass.cpp
index 662f2174ba3c3..409b3670e5bac 100644
--- a/libcxx/test/libcxx/memory/uninitialized_allocator_relocate.pass.cpp
+++ b/libcxx/test/libcxx/memory/uninitialized_allocator_relocate.pass.cpp
@@ -53,6 +53,16 @@ struct Fixture {
Pointer dest;
};
+struct DestroyTracker {
+ explicit DestroyTracker(bool* destroyed) : destroyed_(destroyed) {}
+ DestroyTracker(DestroyTracker&& other) : destroyed_(other.destroyed_) { other.destroyed_ = nullptr; }
+ ~DestroyTracker() {
+ if (destroyed != nullptr)
+ *destroyed_ = true;
+ }
+ bool* destroyed_;
+};
+
template <class Alloc, class Iterator, class OutputIterator>
constexpr void test() {
using T = std::allocator_traits<Alloc>::value_type;
@@ -114,6 +124,7 @@ constexpr void test() {
{
Alloc allocator;
Pointer buff = allocator.allocate(10);
+ // x x x x x 5 6 7 8 9
for (std::size_t i = 5; i != 10; ++i) {
std::allocator_traits<Alloc>::construct(allocator, std::to_address(buff + i), T(i));
}
@@ -132,7 +143,29 @@ constexpr void test() {
allocator.deallocate(buff, 10);
}
- // TODO: Add exception test
+ // Test throwing an exception while we're relocating
+#ifndef TEST_HAS_NO_EXCEPTIONS
+ {
+ // With some overlap between the input and the output
+ {
+ bool destroyed[10] = {false, false, ...};
+ DestroyTracker elements[] = {&destroyed[0], &destroyed[1], ...};
+ try {
+ std::__uninitialized_allocator_relocate(
+ allocator,
+ Iterator(std::to_address(buff + 5)),
+ Iterator(std::to_address(buff + 10)),
+ OutputIterator(std::to_address(buff)));
+ } catch (...) {
+ // TODO: ensure we destroyed everything
+ }
+ }
+
+ // Without overlap
+ {
+ }
+ }
+#endif // TEST_HAS_NO_EXCEPTIONS
}
struct NotTriviallyRelocatable {
More information about the libcxx-commits
mailing list