[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