[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
Wed Nov 27 11:51:49 PST 2024
https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/116714
>From 741987fcbdca9a5df3eb5abcc8ae14fe157f80a0 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Sat, 23 Nov 2024 19:17:36 +0100
Subject: [PATCH 1/7] [libc++] Refactor tests for hijacked address operator in
vector
---
.../vector/addressof.compile.pass.cpp | 47 +++++++++++++++++++
.../assign_copy.addressof.compile.pass.cpp | 24 ----------
.../assign_move.addressof.compile.pass.cpp | 25 ----------
.../move.addressof.compile.pass.cpp | 32 -------------
.../emplace.addressof.compile.pass.cpp | 25 ----------
.../erase_iter.addressof.compile.pass.cpp | 23 ---------
...erase_iter_iter.addressof.compile.pass.cpp | 23 ---------
..._iter_iter_iter.addressof.compile.pass.cpp | 31 ------------
...ert_iter_rvalue.addressof.compile.pass.cpp | 25 ----------
...iter_size_value.addressof.compile.pass.cpp | 24 ----------
...sert_iter_value.addressof.compile.pass.cpp | 24 ----------
.../swap.addressof.compile.pass.cpp | 25 ----------
12 files changed, 47 insertions(+), 281 deletions(-)
create mode 100644 libcxx/test/std/containers/sequences/vector/addressof.compile.pass.cpp
delete mode 100644 libcxx/test/std/containers/sequences/vector/vector.cons/assign_copy.addressof.compile.pass.cpp
delete mode 100644 libcxx/test/std/containers/sequences/vector/vector.cons/assign_move.addressof.compile.pass.cpp
delete mode 100644 libcxx/test/std/containers/sequences/vector/vector.cons/move.addressof.compile.pass.cpp
delete mode 100644 libcxx/test/std/containers/sequences/vector/vector.modifiers/emplace.addressof.compile.pass.cpp
delete mode 100644 libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.addressof.compile.pass.cpp
delete mode 100644 libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.addressof.compile.pass.cpp
delete mode 100644 libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_iter_iter.addressof.compile.pass.cpp
delete mode 100644 libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_rvalue.addressof.compile.pass.cpp
delete mode 100644 libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_size_value.addressof.compile.pass.cpp
delete mode 100644 libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_value.addressof.compile.pass.cpp
delete mode 100644 libcxx/test/std/containers/sequences/vector/vector.special/swap.addressof.compile.pass.cpp
diff --git a/libcxx/test/std/containers/sequences/vector/addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/addressof.compile.pass.cpp
new file mode 100644
index 00000000000000..120b7b289af93e
--- /dev/null
+++ b/libcxx/test/std/containers/sequences/vector/addressof.compile.pass.cpp
@@ -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);
+}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/assign_copy.addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/assign_copy.addressof.compile.pass.cpp
deleted file mode 100644
index ceecdfda3fa304..00000000000000
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/assign_copy.addressof.compile.pass.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-// <vector>
-
-// vector& operator=(const vector& c);
-
-// Validate whether the container can be copy-assigned with an ADL-hijacking operator&
-
-#include <vector>
-
-#include "test_macros.h"
-#include "operator_hijacker.h"
-
-void test() {
- std::vector<operator_hijacker> vo;
- std::vector<operator_hijacker> v;
- v = vo;
-}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/assign_move.addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/assign_move.addressof.compile.pass.cpp
deleted file mode 100644
index 2008c8d048f4b4..00000000000000
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/assign_move.addressof.compile.pass.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-// <vector>
-
-// vector& operator=(vector&& c);
-
-// Validate whether the container can be copy-assigned with an ADL-hijacking operator&
-
-#include <vector>
-#include <utility>
-
-#include "test_macros.h"
-#include "operator_hijacker.h"
-
-void test() {
- std::vector<operator_hijacker> vo;
- std::vector<operator_hijacker> v;
- v = std::move(vo);
-}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/move.addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/move.addressof.compile.pass.cpp
deleted file mode 100644
index 521b8705d49202..00000000000000
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/move.addressof.compile.pass.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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>
-
-// vector(vector&& c);
-
-// Validate whether the container can be copy-assigned with an ADL-hijacking operator&
-
-#include <vector>
-#include <utility>
-
-#include "test_macros.h"
-#include "operator_hijacker.h"
-
-void test() {
- {
- std::vector<operator_hijacker> vo;
- std::vector<operator_hijacker> v(std::move(vo));
- }
- {
- std::vector<operator_hijacker> vo;
- std::vector<operator_hijacker> v(std::move(vo), std::allocator<operator_hijacker>());
- }
-}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/emplace.addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/emplace.addressof.compile.pass.cpp
deleted file mode 100644
index 43e553e71e7414..00000000000000
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/emplace.addressof.compile.pass.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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>
-
-// template <class... Args> iterator emplace(const_iterator pos, Args&&... args);
-
-// Validate whether the container can be copy-assigned with an ADL-hijacking operator&
-
-#include <vector>
-
-#include "test_macros.h"
-#include "operator_hijacker.h"
-
-void test() {
- std::vector<operator_hijacker> v;
- v.emplace(v.end());
-}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.addressof.compile.pass.cpp
deleted file mode 100644
index 0fce3498fec7e8..00000000000000
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.addressof.compile.pass.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-// <vector>
-
-// iterator erase(const_iterator position);
-
-// Validate whether the container can be copy-assigned with an ADL-hijacking operator&
-
-#include <vector>
-
-#include "test_macros.h"
-#include "operator_hijacker.h"
-
-void test() {
- std::vector<operator_hijacker> v;
- v.erase(v.end());
-}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.addressof.compile.pass.cpp
deleted file mode 100644
index bc90fa783e98f5..00000000000000
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.addressof.compile.pass.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-// <vector>
-
-// iterator erase(const_iterator position);
-
-// Validate whether the container can be copy-assigned with an ADL-hijacking operator&
-
-#include <vector>
-
-#include "test_macros.h"
-#include "operator_hijacker.h"
-
-void test() {
- std::vector<operator_hijacker> v;
- v.erase(v.begin(), v.end());
-}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_iter_iter.addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_iter_iter.addressof.compile.pass.cpp
deleted file mode 100644
index f8311090b37e3c..00000000000000
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_iter_iter.addressof.compile.pass.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-// <vector>
-
-// template <class Iter>
-// iterator insert(const_iterator position, Iter first, Iter last);
-
-// Validate whether the container can be copy-assigned with an ADL-hijacking operator&
-
-#include <vector>
-
-#include "test_macros.h"
-#include "operator_hijacker.h"
-#include "test_iterators.h"
-
-void test(cpp17_input_iterator<operator_hijacker*> i) {
- {
- std::vector<operator_hijacker> v;
- v.insert(v.end(), i, i);
- }
- {
- std::vector<operator_hijacker> v;
- v.insert(v.end(), v.begin(), v.end());
- }
-}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_rvalue.addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_rvalue.addressof.compile.pass.cpp
deleted file mode 100644
index 11f24604eeac4d..00000000000000
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_rvalue.addressof.compile.pass.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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>
-
-// iterator insert(const_iterator position, value_type&& x);
-
-// Validate whether the container can be copy-assigned with an ADL-hijacking operator&
-
-#include <vector>
-
-#include "test_macros.h"
-#include "operator_hijacker.h"
-
-void test() {
- std::vector<operator_hijacker> v;
- v.insert(v.end(), operator_hijacker());
-}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_size_value.addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_size_value.addressof.compile.pass.cpp
deleted file mode 100644
index c02b92a4998e85..00000000000000
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_size_value.addressof.compile.pass.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-// <vector>
-
-// iterator insert(const_iterator position, size_type n, const value_type& x);
-
-// Validate whether the container can be copy-assigned with an ADL-hijacking operator&
-
-#include <vector>
-
-#include "test_macros.h"
-#include "operator_hijacker.h"
-
-void test() {
- std::vector<operator_hijacker> v;
- operator_hijacker val;
- v.insert(v.end(), 1, val);
-}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_value.addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_value.addressof.compile.pass.cpp
deleted file mode 100644
index fbf1a4f50b974f..00000000000000
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_value.addressof.compile.pass.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-// <vector>
-
-// iterator insert(const_iterator position, const value_type& x);
-
-// Validate whether the container can be copy-assigned with an ADL-hijacking operator&
-
-#include <vector>
-
-#include "test_macros.h"
-#include "operator_hijacker.h"
-
-void test() {
- std::vector<operator_hijacker> v;
- operator_hijacker val;
- v.insert(v.end(), val);
-}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.special/swap.addressof.compile.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.special/swap.addressof.compile.pass.cpp
deleted file mode 100644
index 4e908d9ff6eaca..00000000000000
--- a/libcxx/test/std/containers/sequences/vector/vector.special/swap.addressof.compile.pass.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-// <vector>
-
-// template <class T, class Alloc>
-// void swap(vector<T,Alloc>& x, vector<T,Alloc>& y);
-
-// Validate whether the container can be copy-assigned with an ADL-hijacking operator&
-
-#include <vector>
-
-#include "test_macros.h"
-#include "operator_hijacker.h"
-
-void test() {
- std::vector<operator_hijacker> vo;
- std::vector<operator_hijacker> v;
- v.swap(vo);
-}
>From 0115849b664a1cea07c0a2a6ff812bc5eecd4782 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/7] [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 | 2 +
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 | 2 +
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 | 5 +
libcxx/include/tuple | 2 +
libcxx/include/variant | 2 +
.../is_replaceable.compile.pass.cpp | 269 ++++++++++++++++++
18 files changed, 385 insertions(+), 2 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 0ae031e5365aef..1f6cb1a22e89d7 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -813,6 +813,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 c43c005603dd7a..b4fb3613b7f890 100644
--- a/libcxx/include/__exception/exception_ptr.h
+++ b/libcxx/include/__exception/exception_ptr.h
@@ -66,8 +66,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 = exception_ptr;
+ using __replaceable = 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 3d3f11967ee746..4c4db9e789e8cb 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,7 @@ 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 = __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 b675e01bac81e5..6179687ebec87e 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 = locale;
+ using __replaceable = 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 5c34f2efc5730e..da34e23878d936 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -315,7 +315,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_;
@@ -1210,7 +1214,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_;
diff --git a/libcxx/include/__memory/unique_ptr.h b/libcxx/include/__memory/unique_ptr.h
index 28c62e13566e24..795db504e8ad0a 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 =
+ __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 =
+ __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 a44811c766735a..30668032551bfc 100644
--- a/libcxx/include/__split_buffer
+++ b/libcxx/include/__split_buffer
@@ -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>
@@ -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_;
diff --git a/libcxx/include/__type_traits/is_replaceable.h b/libcxx/include/__type_traits/is_replaceable.h
new file mode 100644
index 00000000000000..46eb6f79ecb69d
--- /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 f9d0f4e4723113..811c5fb9cf7411 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,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;
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index ae3ea1de61de01..26a564995730ff 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -51,6 +51,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>
@@ -118,6 +119,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,
diff --git a/libcxx/include/array b/libcxx/include/array
index 99b38d049e129d..b92d81cce69a77 100644
--- a/libcxx/include/array
+++ b/libcxx/include/array
@@ -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>
@@ -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;
diff --git a/libcxx/include/deque b/libcxx/include/deque
index ad667503489741..8dfa7c733ee4e3 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -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>
@@ -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,
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 4e06a68c6a6b61..dea1d294f54516 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -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
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 7ad6a9e116941f..a2425323dbcd49 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -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>
@@ -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.
diff --git a/libcxx/include/string b/libcxx/include/string
index bf7fc3c37ecd7a..cd90a15ef56619 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -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>
@@ -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 {
diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index c3f7b8041686d1..1ed88526753989 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -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>
@@ -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]
diff --git a/libcxx/include/variant b/libcxx/include/variant
index f604527cd22569..7457ffa26fba81 100644
--- a/libcxx/include/variant
+++ b/libcxx/include/variant
@@ -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>
@@ -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>
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 00000000000000..c52f5462f8bb3f
--- /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 14c47bd75927c8d5476818f9477ee41c94b48a06 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Sat, 23 Nov 2024 18:16:56 -0500
Subject: [PATCH 3/7] [libc++] Extract destroy algorithms into a single header
This is useful to start using those algorithms from the upcoming
relocation utilities.
---
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__memory/construct_at.h | 37 +---------
libcxx/include/__memory/destroy.h | 70 +++++++++++++++++++
libcxx/include/__memory/ranges_construct_at.h | 1 +
libcxx/include/__memory/shared_ptr.h | 1 +
.../__memory/uninitialized_algorithms.h | 9 +--
libcxx/include/__pstl/backends/libdispatch.h | 1 +
libcxx/include/module.modulemap | 1 +
8 files changed, 79 insertions(+), 42 deletions(-)
create mode 100644 libcxx/include/__memory/destroy.h
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 1f6cb1a22e89d7..94f5dfd572e01a 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -549,6 +549,7 @@ set(files
__memory/compressed_pair.h
__memory/concepts.h
__memory/construct_at.h
+ __memory/destroy.h
__memory/destruct_n.h
__memory/inout_ptr.h
__memory/noexcept_move_assign_container.h
diff --git a/libcxx/include/__memory/construct_at.h b/libcxx/include/__memory/construct_at.h
index d8c97467f54b9f..056984403639a9 100644
--- a/libcxx/include/__memory/construct_at.h
+++ b/libcxx/include/__memory/construct_at.h
@@ -57,9 +57,6 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp* __construct_at(_Tp* __l
// The internal functions are available regardless of the language version (with the exception of the `__destroy_at`
// taking an array).
-template <class _ForwardIterator>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator __destroy(_ForwardIterator, _ForwardIterator);
-
template <class _Tp, __enable_if_t<!is_array<_Tp>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __destroy_at(_Tp* __loc) {
_LIBCPP_ASSERT_NON_NULL(__loc != nullptr, "null pointer given to destroy_at");
@@ -70,28 +67,12 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __destroy_at(_Tp* __loc
template <class _Tp, __enable_if_t<is_array<_Tp>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __destroy_at(_Tp* __loc) {
_LIBCPP_ASSERT_NON_NULL(__loc != nullptr, "null pointer given to destroy_at");
- std::__destroy(std::begin(*__loc), std::end(*__loc));
+ auto const __end = std::end(*__loc);
+ for (auto __it = std::begin(*__loc); __it != __end; ++__it)
+ std::__destroy_at(*__it);
}
#endif
-template <class _ForwardIterator>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator
-__destroy(_ForwardIterator __first, _ForwardIterator __last) {
- for (; __first != __last; ++__first)
- std::__destroy_at(std::addressof(*__first));
- return __first;
-}
-
-template <class _BidirectionalIterator>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _BidirectionalIterator
-__reverse_destroy(_BidirectionalIterator __first, _BidirectionalIterator __last) {
- while (__last != __first) {
- --__last;
- std::__destroy_at(std::addressof(*__last));
- }
- return __last;
-}
-
#if _LIBCPP_STD_VER >= 17
template <class _Tp, enable_if_t<!is_array_v<_Tp>, int> = 0>
@@ -106,18 +87,6 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void destroy_at(_Tp* __loc)
}
# endif
-template <class _ForwardIterator>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void destroy(_ForwardIterator __first, _ForwardIterator __last) {
- (void)std::__destroy(std::move(__first), std::move(__last));
-}
-
-template <class _ForwardIterator, class _Size>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator destroy_n(_ForwardIterator __first, _Size __n) {
- for (; __n > 0; (void)++__first, --__n)
- std::__destroy_at(std::addressof(*__first));
- return __first;
-}
-
#endif // _LIBCPP_STD_VER >= 17
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/__memory/destroy.h b/libcxx/include/__memory/destroy.h
new file mode 100644
index 00000000000000..48d895827553b6
--- /dev/null
+++ b/libcxx/include/__memory/destroy.h
@@ -0,0 +1,70 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_DESTROY_H
+#define _LIBCPP___MEMORY_DESTROY_H
+
+#include <__config>
+#include <__memory/addressof.h>
+#include <__memory/allocator_traits.h>
+#include <__memory/construct_at.h>
+#include <__memory/pointer_traits.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
+
+template <class _ForwardIterator>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator
+__destroy(_ForwardIterator __first, _ForwardIterator __last) {
+ for (; __first != __last; ++__first)
+ std::__destroy_at(std::addressof(*__first));
+ return __first;
+}
+
+template <class _BidirectionalIterator>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _BidirectionalIterator
+__reverse_destroy(_BidirectionalIterator __first, _BidirectionalIterator __last) {
+ while (__last != __first) {
+ --__last;
+ std::__destroy_at(std::addressof(*__last));
+ }
+ return __last;
+}
+
+// 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 _ForwardIterator>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void destroy(_ForwardIterator __first, _ForwardIterator __last) {
+ (void)std::__destroy(std::move(__first), std::move(__last));
+}
+
+template <class _ForwardIterator, class _Size>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator destroy_n(_ForwardIterator __first, _Size __n) {
+ for (; __n > 0; (void)++__first, --__n)
+ std::__destroy_at(std::addressof(*__first));
+ return __first;
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___MEMORY_DESTROY_H
diff --git a/libcxx/include/__memory/ranges_construct_at.h b/libcxx/include/__memory/ranges_construct_at.h
index b7523d4ba4b81b..39f67cecf22853 100644
--- a/libcxx/include/__memory/ranges_construct_at.h
+++ b/libcxx/include/__memory/ranges_construct_at.h
@@ -16,6 +16,7 @@
#include <__iterator/iterator_traits.h>
#include <__memory/concepts.h>
#include <__memory/construct_at.h>
+#include <__memory/destroy.h>
#include <__ranges/access.h>
#include <__ranges/concepts.h>
#include <__ranges/dangling.h>
diff --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index da34e23878d936..55900d45c09bb5 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -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>
diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index 71c7ed94fec13e..f2e2e0f462051d 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -18,6 +18,7 @@
#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/pointer_traits.h>
@@ -511,14 +512,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:
diff --git a/libcxx/include/__pstl/backends/libdispatch.h b/libcxx/include/__pstl/backends/libdispatch.h
index 701367b505c8b7..bc759c607d0e48 100644
--- a/libcxx/include/__pstl/backends/libdispatch.h
+++ b/libcxx/include/__pstl/backends/libdispatch.h
@@ -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>
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index dea1d294f54516..7991bfda8db9aa 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -1535,6 +1535,7 @@ 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" }
>From 20fd6fcef56871016aeffe3cb3f44beec9ef5e3c 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 4/7] [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 | 213 ++++++++++++++++++
libcxx/include/__vector/vector.h | 6 +-
libcxx/include/module.modulemap | 5 +
7 files changed, 373 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
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 94f5dfd572e01a..1bd94017e4094d 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -552,18 +552,21 @@ 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
__memory/ranges_construct_at.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 00000000000000..64563b3d7a92df
--- /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 00000000000000..2d5deb1e84035b
--- /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* __source, _Tp* __dest) {
+ 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* __source, _Tp* __dest) {
+ 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(__source, __dest);
+ } 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 f2e2e0f462051d..6e4951813e936a 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -21,6 +21,7 @@
#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>
@@ -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 00000000000000..1193cc44e5235e
--- /dev/null
+++ b/libcxx/include/__memory/uninitialized_relocate.h
@@ -0,0 +1,213 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/allocator_traits.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
+
+// TODO: We currently use std::__to_address but we don't guarantee contiguous iterators. How does that work?
+
+// __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 _InputIter
+__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) {
+ // 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::__to_address(__first), std::__to_address(__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 _BidirectionalIter __uninitialized_relocate_backward(
+ _BidirectionalIter __first, _BidirectionalIter __last, _NothrowBidirectionalIter __result_last) {
+ if constexpr (__libcpp_is_contiguous_iterator<_BidirectionalIter>::value &&
+ __libcpp_is_contiguous_iterator<_NothrowBidirectionalIter>::value) {
+ // TODO: Check for off-by-one here, we might want to check __result_last - 1
+ _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(
+ !std::__is_pointer_in_range(
+ std::__to_address(__first), std::__to_address(__last), std::__to_address(__result_last)),
+ "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) {
+ 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::__to_address(__last), std::__to_address(__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::__to_address(__first), std::__to_address(__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) {
+ // TODO: Off by one?
+ _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(
+ !std::__is_pointer_in_range(
+ std::__to_address(__first), std::__to_address(__last), std::__to_address(__result_last)),
+ "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::__to_address(__last), std::__to_address(__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
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index 26a564995730ff..2ab3300f71dae4 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -811,7 +811,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.
@@ -834,13 +834,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 7991bfda8db9aa..7fe0d33346b000 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -1539,6 +1539,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" }
@@ -1548,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" }
@@ -1560,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"
}
>From 4986d1cdd94f49e690d8f1e08f878c2fa34933de 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 5/7] TODO: Improve the tests for insert and emplace
---
.../vector/vector.modifiers/emplace.pass.cpp | 13 +++++++++++++
1 file changed, 13 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 4f5e36abb6bb8d..05cbe3631a97c9 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
@@ -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?
>From b3b3d0596c8d95f95e5214523dd64d66bc52c68b 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 6/7] [WIP] Implement std::vector operations in terms of
relocation
---
libcxx/docs/ReleaseNotes/20.rst | 3 +
libcxx/include/__memory/temp_value.h | 18 ++
libcxx/include/__vector/vector.h | 191 ++++++++++--------
.../vector/vector.modifiers/common.h | 22 --
.../vector.modifiers/erase_iter.pass.cpp | 26 ---
.../vector.modifiers/erase_iter_iter.pass.cpp | 26 ---
6 files changed, 123 insertions(+), 163 deletions(-)
diff --git a/libcxx/docs/ReleaseNotes/20.rst b/libcxx/docs/ReleaseNotes/20.rst
index 9039c6f046445b..fdb862aa69da9f 100644
--- a/libcxx/docs/ReleaseNotes/20.rst
+++ b/libcxx/docs/ReleaseNotes/20.rst
@@ -52,6 +52,9 @@ Improvements and New Features
- The ``lexicographical_compare`` and ``ranges::lexicographical_compare`` algorithms have been optimized for trivially
equality comparable types, resulting in a performance improvement of up to 40x.
+- 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).
+
- The ``_LIBCPP_ENABLE_CXX20_REMOVED_TEMPORARY_BUFFER`` macro has been added to make ``std::get_temporary_buffer`` and
``std::return_temporary_buffer`` available.
diff --git a/libcxx/include/__memory/temp_value.h b/libcxx/include/__memory/temp_value.h
index 4a133b3fbcf6c0..bd23de3aef8320 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 2ab3300f71dae4..701d00c6fcdf4e 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -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>
@@ -472,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);
@@ -520,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();
@@ -662,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);
@@ -1115,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) {
@@ -1152,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) {
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 72cd47a50b2c0d..24627d9fe5f450 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 f0157eb74b90f3..b2948ba5a46cd1 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 104dfb4cb07d4f..fc00a720ece2f7 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 892ed1badb94a4c165339a816cd24f45462638da Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Sat, 23 Nov 2024 22:13:19 +0100
Subject: [PATCH 7/7] Fixes to the base facilities
---
libcxx/include/__memory/destroy.h | 3 +-
libcxx/include/__memory/relocate_at.h | 2 +-
.../include/__memory/uninitialized_relocate.h | 44 +++--
.../uninitialized_allocator_relocate.pass.cpp | 183 ++++++++++++++++++
4 files changed, 208 insertions(+), 24 deletions(-)
create mode 100644 libcxx/test/libcxx/memory/uninitialized_allocator_relocate.pass.cpp
diff --git a/libcxx/include/__memory/destroy.h b/libcxx/include/__memory/destroy.h
index 48d895827553b6..6cea2f525b5c4b 100644
--- a/libcxx/include/__memory/destroy.h
+++ b/libcxx/include/__memory/destroy.h
@@ -13,7 +13,6 @@
#include <__memory/addressof.h>
#include <__memory/allocator_traits.h>
#include <__memory/construct_at.h>
-#include <__memory/pointer_traits.h>
#include <__utility/move.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -48,7 +47,7 @@ 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));
+ allocator_traits<_Alloc>::destroy(__alloc, std::addressof(*__first));
}
template <class _ForwardIterator>
diff --git a/libcxx/include/__memory/relocate_at.h b/libcxx/include/__memory/relocate_at.h
index 2d5deb1e84035b..24289cbf080e4f 100644
--- a/libcxx/include/__memory/relocate_at.h
+++ b/libcxx/include/__memory/relocate_at.h
@@ -49,7 +49,7 @@ _LIBCPP_HIDE_FROM_ABI _Tp* __libcpp_builtin_trivially_relocate_at(_Tp* __source,
}
template <class _Tp>
-_LIBCPP_HIDE_FROM_ABI _Tp* __libcpp_builtin_trivially_relocate_at(_Tp* __first, Tp* __last, _Tp* __dest) _NOEXCEPT {
+_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));
diff --git a/libcxx/include/__memory/uninitialized_relocate.h b/libcxx/include/__memory/uninitialized_relocate.h
index 1193cc44e5235e..e2747b34e541f7 100644
--- a/libcxx/include/__memory/uninitialized_relocate.h
+++ b/libcxx/include/__memory/uninitialized_relocate.h
@@ -11,7 +11,9 @@
#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>
@@ -30,8 +32,6 @@ _LIBCPP_PUSH_MACROS
_LIBCPP_BEGIN_NAMESPACE_STD
-// TODO: We currently use std::__to_address but we don't guarantee contiguous iterators. How does that work?
-
// __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,
@@ -49,7 +49,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD
// - __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 _InputIter
+_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) {
@@ -64,10 +64,12 @@ __uninitialized_relocate(_InputIter __first, _InputIter __last, _NothrowForwardI
if constexpr (__libcpp_is_contiguous_iterator<_InputIter>::value &&
__libcpp_is_contiguous_iterator<_NothrowForwardIter>::value &&
__libcpp_is_trivially_relocatable<_ValueType>::value) {
- // 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);
+ 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.
@@ -77,7 +79,7 @@ __uninitialized_relocate(_InputIter __first, _InputIter __last, _NothrowForwardI
auto const __first_result = __result;
try {
while (__first != __last) {
- std::__relocate_at(std::__to_address(__first), std::__to_address(__result));
+ std::__relocate_at(std::addressof(*__first), std::addressof(*__result));
++__first;
++__result;
}
@@ -93,14 +95,13 @@ __uninitialized_relocate(_InputIter __first, _InputIter __last, _NothrowForwardI
// 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 _BidirectionalIter __uninitialized_relocate_backward(
+_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) {
- // TODO: Check for off-by-one here, we might want to check __result_last - 1
_LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(
!std::__is_pointer_in_range(
- std::__to_address(__first), std::__to_address(__last), std::__to_address(__result_last)),
+ 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;
@@ -110,11 +111,13 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _BidirectionalIter __uniniti
if constexpr (__libcpp_is_contiguous_iterator<_BidirectionalIter>::value &&
__libcpp_is_contiguous_iterator<_NothrowBidirectionalIter>::value &&
__libcpp_is_trivially_relocatable<_ValueType>::value) {
- 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;
+ 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.
@@ -126,7 +129,7 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _BidirectionalIter __uniniti
while (__last != __first) {
--__last;
--__result;
- std::__relocate_at(std::__to_address(__last), std::__to_address(__result));
+ std::__relocate_at(std::addressof(*__last), std::addressof(*__result));
}
} catch (...) {
std::destroy(__first, __last);
@@ -156,7 +159,7 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _NothrowForwardIter __uninit
auto const __first_result = __result;
try {
while (__first != __last) {
- std::__allocator_relocate_at(__alloc, std::__to_address(__first), std::__to_address(__result));
+ std::__allocator_relocate_at(__alloc, std::addressof(*__first), std::addressof(*__result));
++__first;
++__result;
}
@@ -176,10 +179,9 @@ __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) {
- // TODO: Off by one?
_LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(
!std::__is_pointer_in_range(
- std::__to_address(__first), std::__to_address(__last), std::__to_address(__result_last)),
+ 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");
}
@@ -194,7 +196,7 @@ __uninitialized_allocator_relocate_backward(
while (__last != __first) {
--__last;
--__result;
- std::__allocator_relocate_at(__alloc, std::__to_address(__last), std::__to_address(__result));
+ std::__allocator_relocate_at(__alloc, std::addressof(*__last), std::addressof(*__result));
}
} catch (...) {
std::__allocator_destroy(__alloc, __first, __last);
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 00000000000000..662f2174ba3c3b
--- /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;
+}
More information about the libcxx-commits
mailing list