[libcxx-commits] [libcxx] [libc++] Optimize vector::erase (PR #213916)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Tue Aug 4 07:37:10 PDT 2026


https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/213916

>From 295e77b7d02f04b3c1a4ad414f67c6b62a2cfda2 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Mon, 3 Aug 2026 15:05:19 +0200
Subject: [PATCH 1/2] [libc++] Optimize vector::erase

---
 libcxx/include/CMakeLists.txt                 |  2 +-
 libcxx/include/__expected/expected.h          |  2 +-
 .../__memory/uninitialized_algorithms.h       | 44 +++++++++++-
 libcxx/include/__memory/unique_ptr.h          |  2 +-
 libcxx/include/__optional/optional.h          |  2 +-
 libcxx/include/__optional/optional_ref.h      |  2 +-
 libcxx/include/__split_buffer                 |  2 +-
 ...ivially_relocatable.h => is_relocatable.h} | 13 +++-
 libcxx/include/__utility/pair.h               |  2 +-
 libcxx/include/__vector/layout.h              | 26 +++++--
 libcxx/include/__vector/vector.h              | 52 +++++++-------
 libcxx/include/array                          |  2 +-
 libcxx/include/deque                          |  2 +-
 libcxx/include/module.modulemap.in            |  8 +--
 libcxx/include/string                         |  2 +-
 libcxx/include/tuple                          |  2 +-
 libcxx/include/variant                        |  2 +-
 .../is_trivially_relocatable.compile.pass.cpp |  2 +-
 .../vector/vector.modifiers/common.h          | 17 +++--
 .../vector.modifiers/erase_iter.pass.cpp      | 69 +++++++++++-------
 .../vector.modifiers/erase_iter_iter.pass.cpp | 71 ++++++++++++-------
 21 files changed, 217 insertions(+), 109 deletions(-)
 rename libcxx/include/__type_traits/{is_trivially_relocatable.h => is_relocatable.h} (80%)

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index dc1d8bb26bac4..bde6191f94ea6 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -907,7 +907,7 @@ set(files
   __type_traits/is_trivially_copyable.h
   __type_traits/is_trivially_destructible.h
   __type_traits/is_trivially_lexicographically_comparable.h
-  __type_traits/is_trivially_relocatable.h
+  __type_traits/is_relocatable.h
   __type_traits/is_union.h
   __type_traits/is_unqualified.h
   __type_traits/is_unsigned.h
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 987ccf628c814..5588196939738 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -31,11 +31,11 @@
 #include <__type_traits/is_nothrow_assignable.h>
 #include <__type_traits/is_nothrow_constructible.h>
 #include <__type_traits/is_reference.h>
+#include <__type_traits/is_relocatable.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/is_trivially_constructible.h>
 #include <__type_traits/is_trivially_destructible.h>
-#include <__type_traits/is_trivially_relocatable.h>
 #include <__type_traits/is_void.h>
 #include <__type_traits/negation.h>
 #include <__type_traits/remove_cv.h>
diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index e1c6fe25040ea..ad44dea679f98 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -26,10 +26,10 @@
 #include <__type_traits/enable_if.h>
 #include <__type_traits/is_constant_evaluated.h>
 #include <__type_traits/is_reference.h>
+#include <__type_traits/is_relocatable.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_trivially_assignable.h>
 #include <__type_traits/is_trivially_constructible.h>
-#include <__type_traits/is_trivially_relocatable.h>
 #include <__type_traits/remove_const.h>
 #include <__utility/exception_guard.h>
 #include <__utility/move.h>
@@ -176,7 +176,7 @@ uninitialized_default_construct(_ForwardIterator __first, _ForwardIterator __las
 template <class _ValueType, class _ForwardIterator, class _Size>
 inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 _ForwardIterator
 __uninitialized_default_construct_n(_ForwardIterator __first, _Size __n) {
-  auto __idx = __first;
+  auto __idx   = __first;
   auto __guard = std::__make_exception_guard([&] { std::__destroy(__first, __idx); });
   for (; __n > 0; ++__idx, (void)--__n)
     ::new (static_cast<void*>(std::addressof(*__idx))) _ValueType;
@@ -218,7 +218,7 @@ uninitialized_value_construct(_ForwardIterator __first, _ForwardIterator __last)
 template <class _ValueType, class _ForwardIterator, class _Size>
 inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 _ForwardIterator
 __uninitialized_value_construct_n(_ForwardIterator __first, _Size __n) {
-  auto __idx = __first;
+  auto __idx   = __first;
   auto __guard = std::__make_exception_guard([&] { std::__destroy(__first, __idx); });
   for (; __n > 0; ++__idx, (void)--__n)
     ::new (static_cast<void*>(std::addressof(*__idx))) _ValueType();
@@ -431,6 +431,44 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void __uninitialized_allocat
   }
 }
 
+// __uninitialized_allocator_relocate_forward relocates the objects in [__first, __last) into __result
+// element-by-element from __first to __last. 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.
+//
+// Preconditions:  At __result there is no object and [__first, __last) contains objects
+// Postconditions: __result contains the objects from [__first, __last) and
+//                 [__first, __last) doesn't contain any objects
+//
+// The strong exception guarantee is provided.
+template <class _Alloc, class _ContiguousIterator>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void __uninitialized_allocator_relocate_forward(
+    _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_nothrow_relocatable_v<_ValueType> && __allocator_has_trivial_move_construct_v<_Alloc, _ValueType> &&
+      __allocator_has_trivial_destroy_v<_Alloc, _ValueType>);
+  if (__libcpp_is_constant_evaluated() || !__is_trivially_relocatable_v<_ValueType>) {
+    auto __iter = __first;
+    while (__iter != __last) {
+#if _LIBCPP_HAS_EXCEPTIONS
+      allocator_traits<_Alloc>::construct(__alloc, std::__to_address(__result), std::move_if_noexcept(*__iter));
+#else
+      allocator_traits<_Alloc>::construct(__alloc, std::__to_address(__result), std::move(*__iter));
+#endif
+      std::__destroy_at(std::__to_address(__iter));
+      ++__iter;
+      ++__result;
+    }
+  } else {
+    // Casting to void* to suppress clang complaining that this is technically UB.
+    __builtin_memmove(static_cast<void*>(std::__to_address(__result)),
+                      std::__to_address(__first),
+                      sizeof(_ValueType) * (__last - __first));
+  }
+}
+
 _LIBCPP_END_NAMESPACE_STD
 
 _LIBCPP_POP_MACROS
diff --git a/libcxx/include/__memory/unique_ptr.h b/libcxx/include/__memory/unique_ptr.h
index 63f62137ed8a2..acece3ae61f1d 100644
--- a/libcxx/include/__memory/unique_ptr.h
+++ b/libcxx/include/__memory/unique_ptr.h
@@ -37,9 +37,9 @@
 #include <__type_traits/is_function.h>
 #include <__type_traits/is_pointer.h>
 #include <__type_traits/is_reference.h>
+#include <__type_traits/is_relocatable.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_swappable.h>
-#include <__type_traits/is_trivially_relocatable.h>
 #include <__type_traits/is_void.h>
 #include <__type_traits/remove_extent.h>
 #include <__type_traits/remove_reference.h>
diff --git a/libcxx/include/__optional/optional.h b/libcxx/include/__optional/optional.h
index 8d6a9fbed8599..101db832023f1 100644
--- a/libcxx/include/__optional/optional.h
+++ b/libcxx/include/__optional/optional.h
@@ -40,13 +40,13 @@
 #include <__type_traits/is_nothrow_constructible.h>
 #include <__type_traits/is_object.h>
 #include <__type_traits/is_reference.h>
+#include <__type_traits/is_relocatable.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_scalar.h>
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/is_trivially_assignable.h>
 #include <__type_traits/is_trivially_constructible.h>
 #include <__type_traits/is_trivially_destructible.h>
-#include <__type_traits/is_trivially_relocatable.h>
 #include <__type_traits/negation.h>
 #include <__type_traits/remove_cv.h>
 #include <__type_traits/remove_cvref.h>
diff --git a/libcxx/include/__optional/optional_ref.h b/libcxx/include/__optional/optional_ref.h
index d137b3c40c7e6..c1bbe622891d5 100644
--- a/libcxx/include/__optional/optional_ref.h
+++ b/libcxx/include/__optional/optional_ref.h
@@ -31,8 +31,8 @@
 #include <__type_traits/is_nothrow_constructible.h>
 #include <__type_traits/is_object.h>
 #include <__type_traits/is_reference.h>
+#include <__type_traits/is_relocatable.h>
 #include <__type_traits/is_same.h>
-#include <__type_traits/is_trivially_relocatable.h>
 #include <__type_traits/reference_constructs_from_temporary.h>
 #include <__type_traits/remove_cv.h>
 #include <__type_traits/remove_cvref.h>
diff --git a/libcxx/include/__split_buffer b/libcxx/include/__split_buffer
index 59cee84536069..d89264186d77e 100644
--- a/libcxx/include/__split_buffer
+++ b/libcxx/include/__split_buffer
@@ -31,9 +31,9 @@
 #include <__type_traits/integral_constant.h>
 #include <__type_traits/is_nothrow_assignable.h>
 #include <__type_traits/is_nothrow_constructible.h>
+#include <__type_traits/is_relocatable.h>
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/is_trivially_destructible.h>
-#include <__type_traits/is_trivially_relocatable.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
 
diff --git a/libcxx/include/__type_traits/is_trivially_relocatable.h b/libcxx/include/__type_traits/is_relocatable.h
similarity index 80%
rename from libcxx/include/__type_traits/is_trivially_relocatable.h
rename to libcxx/include/__type_traits/is_relocatable.h
index 11d7d5c153678..e9b6ab53e9047 100644
--- a/libcxx/include/__type_traits/is_trivially_relocatable.h
+++ b/libcxx/include/__type_traits/is_relocatable.h
@@ -6,11 +6,13 @@
 //
 //===----------------------------------------------------------------------===//
 
-#ifndef _LIBCPP___TYPE_TRAITS_IS_TRIVIALLY_RELOCATABLE_H
-#define _LIBCPP___TYPE_TRAITS_IS_TRIVIALLY_RELOCATABLE_H
+#ifndef _LIBCPP___TYPE_TRAITS_IS_RELOCATABLE_H
+#define _LIBCPP___TYPE_TRAITS_IS_RELOCATABLE_H
 
 #include <__config>
 #include <__type_traits/enable_if.h>
+#include <__type_traits/is_nothrow_constructible.h>
+#include <__type_traits/is_nothrow_destructible.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_trivially_copyable.h>
 
@@ -43,6 +45,11 @@ inline const bool
         true;
 #endif
 
+template <class _Tp>
+inline const bool __is_nothrow_relocatable_v =
+    __is_trivially_relocatable_v<_Tp> ||
+    (is_nothrow_move_constructible<_Tp>::value && is_nothrow_destructible<_Tp>::value);
+
 _LIBCPP_END_NAMESPACE_STD
 
-#endif // _LIBCPP___TYPE_TRAITS_IS_TRIVIALLY_RELOCATABLE_H
+#endif // _LIBCPP___TYPE_TRAITS_IS_RELOCATABLE_H
diff --git a/libcxx/include/__utility/pair.h b/libcxx/include/__utility/pair.h
index f9d4599a5068d..a65c178a9f036 100644
--- a/libcxx/include/__utility/pair.h
+++ b/libcxx/include/__utility/pair.h
@@ -31,8 +31,8 @@
 #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_relocatable.h>
 #include <__type_traits/is_swappable.h>
-#include <__type_traits/is_trivially_relocatable.h>
 #include <__type_traits/nat.h>
 #include <__type_traits/unwrap_ref.h>
 #include <__utility/declval.h>
diff --git a/libcxx/include/__vector/layout.h b/libcxx/include/__vector/layout.h
index af03556dc2636..2d1a5f1be54d2 100644
--- a/libcxx/include/__vector/layout.h
+++ b/libcxx/include/__vector/layout.h
@@ -114,12 +114,13 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 template <class _Tp, class _Allocator>
 class __vector_layout {
 public:
-  using value_type _LIBCPP_NODEBUG     = _Tp;
-  using allocator_type _LIBCPP_NODEBUG = _Allocator;
-  using __alloc_traits _LIBCPP_NODEBUG = allocator_traits<allocator_type>;
-  using size_type _LIBCPP_NODEBUG      = typename __alloc_traits::size_type;
-  using pointer _LIBCPP_NODEBUG        = typename __alloc_traits::pointer;
-  using const_pointer _LIBCPP_NODEBUG  = typename __alloc_traits::const_pointer;
+  using value_type _LIBCPP_NODEBUG      = _Tp;
+  using allocator_type _LIBCPP_NODEBUG  = _Allocator;
+  using __alloc_traits _LIBCPP_NODEBUG  = allocator_traits<allocator_type>;
+  using size_type _LIBCPP_NODEBUG       = typename __alloc_traits::size_type;
+  using difference_type _LIBCPP_NODEBUG = typename __alloc_traits::difference_type;
+  using pointer _LIBCPP_NODEBUG         = typename __alloc_traits::pointer;
+  using const_pointer _LIBCPP_NODEBUG   = typename __alloc_traits::const_pointer;
 #ifdef _LIBCPP_ABI_VECTOR_LAYOUT_SIZE_BASED
   using _SplitBuffer _LIBCPP_NODEBUG = __split_buffer<_Tp, _Allocator, __split_buffer_size_layout>;
   using __bound_type _LIBCPP_NODEBUG = size_type;
@@ -206,6 +207,8 @@ class __vector_layout {
   [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_pointer __capacity_ptr() const _NOEXCEPT;
   [[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __invariants() const _NOEXCEPT;
 
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 void __offset_size(difference_type __offset) _NOEXCEPT;
+
 private:
   pointer __begin_ = nullptr;
 
@@ -349,6 +352,11 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __vector_layout<_Tp, _Alloc>::__invariants()
     return __size_ == 0 && __capacity_ == 0;
   return __size_ <= __capacity_;
 }
+
+template <class _Tp, class _Alloc>
+_LIBCPP_CONSTEXPR_SINCE_CXX20 void __vector_layout<_Tp, _Alloc>::__offset_size(difference_type __offset) _NOEXCEPT {
+  __size_ += __offset;
+}
 #else  // !defined(_LIBCPP_ABI_VECTOR_LAYOUT_SIZE_BASED)
 template <class _Tp, class _Alloc>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 __vector_layout<_Tp, _Alloc>::__vector_layout(__vector_layout&& __other)
@@ -470,6 +478,12 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __vector_layout<_Tp, _Alloc>::__invariants()
     return false;
   return __end_ <= __capacity_;
 }
+
+template <class _Tp, class _Alloc>
+_LIBCPP_CONSTEXPR_SINCE_CXX20 void __vector_layout<_Tp, _Alloc>::__offset_size(difference_type __offset) _NOEXCEPT {
+  __end_ += __offset;
+}
+
 #endif // _LIBCPP_ABI_VECTOR_LAYOUT_SIZE_BASED
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index 6297d1952e3c6..7413fc66bfe88 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -55,9 +55,9 @@
 #include <__type_traits/is_nothrow_assignable.h>
 #include <__type_traits/is_nothrow_constructible.h>
 #include <__type_traits/is_pointer.h>
+#include <__type_traits/is_relocatable.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_swappable.h>
-#include <__type_traits/is_trivially_relocatable.h>
 #include <__type_traits/type_identity.h>
 #include <__utility/declval.h>
 #include <__utility/exception_guard.h>
@@ -552,8 +552,32 @@ class 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 __first, const_iterator __last) {
+    _LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "vector::erase(first, last) called with invalid range");
+    pointer __pos    = this->__layout_.__begin_ptr() + (__first - begin());
+    auto __raw_pos   = std::__to_address(__pos);
+    auto __hole_size = __last - __first;
+    if (__first != __last) {
+#ifndef _LIBCPP_CXX03_LANG
+      if constexpr (__is_nothrow_relocatable_v<value_type> &&
+                    __allocator_has_trivial_move_construct_v<allocator_type, value_type> &&
+                    __allocator_has_trivial_destroy_v<allocator_type, value_type>) {
+        std::__destroy_at(__raw_pos);
+        std::__uninitialized_allocator_relocate_forward(
+            __layout_.__alloc(), __raw_pos + __hole_size, std::__to_address(__layout_.__end_ptr()), __raw_pos);
+        __layout_.__offset_size(-__hole_size);
+      } else
+#endif
+        this->__destruct_at_end(std::move(__pos + __hole_size, __layout_.__end_ptr(), __pos));
+    }
+    return __make_iter(__pos);
+  }
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void clear() _NOEXCEPT {
     size_type __old_size = size();
@@ -1091,28 +1115,6 @@ vector<_Tp, _Alloc>::emplace_back(_Args&&... __args) {
 #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->__layout_.__begin_ptr() + __ps;
-  this->__destruct_at_end(std::move(__p + 1, __layout_.__end_ptr(), __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->__layout_.__begin_ptr() + (__first - begin());
-  if (__first != __last) {
-    this->__destruct_at_end(std::move(__p + (__last - __first), __layout_.__end_ptr(), __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) {
diff --git a/libcxx/include/array b/libcxx/include/array
index 851a220e87398..470e268e75466 100644
--- a/libcxx/include/array
+++ b/libcxx/include/array
@@ -134,9 +134,9 @@ 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_relocatable.h>
 #  include <__type_traits/is_same.h>
 #  include <__type_traits/is_swappable.h>
-#  include <__type_traits/is_trivially_relocatable.h>
 #  include <__type_traits/remove_cv.h>
 #  include <__utility/empty.h>
 #  include <__utility/integer_sequence.h>
diff --git a/libcxx/include/deque b/libcxx/include/deque
index 5ef019e0c65f3..9fef85240fd25 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -228,9 +228,9 @@ 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_relocatable.h>
 #  include <__type_traits/is_same.h>
 #  include <__type_traits/is_swappable.h>
-#  include <__type_traits/is_trivially_relocatable.h>
 #  include <__type_traits/type_identity.h>
 #  include <__utility/exception_guard.h>
 #  include <__utility/forward.h>
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index f56d48784737b..03725562a5c68 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -263,6 +263,10 @@ module std_core {
       header "__type_traits/is_reference.h"
       export std_core.type_traits.integral_constant
     }
+    module is_relocatable {
+      header "__type_traits/is_relocatable.h"
+      export std_core.type_traits.integral_constant
+    }
     module is_same {
       header "__type_traits/is_same.h"
       export std_core.type_traits.integral_constant
@@ -311,10 +315,6 @@ module std_core {
       header "__type_traits/is_trivially_lexicographically_comparable.h"
       export std_core.type_traits.integral_constant
     }
-    module is_trivially_relocatable {
-      header "__type_traits/is_trivially_relocatable.h"
-      export std_core.type_traits.integral_constant
-    }
     module is_union {
       header "__type_traits/is_union.h"
       export std_core.type_traits.integral_constant
diff --git a/libcxx/include/string b/libcxx/include/string
index f4c2d846f536d..3ac8e541d8306 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -637,11 +637,11 @@ basic_string<char32_t> operator""s( const char32_t *str, size_t len );
 #  include <__type_traits/is_generic_transparent_comparator.h>
 #  include <__type_traits/is_nothrow_assignable.h>
 #  include <__type_traits/is_nothrow_constructible.h>
+#  include <__type_traits/is_relocatable.h>
 #  include <__type_traits/is_same.h>
 #  include <__type_traits/is_standard_layout.h>
 #  include <__type_traits/is_trivially_constructible.h>
 #  include <__type_traits/is_trivially_copyable.h>
-#  include <__type_traits/is_trivially_relocatable.h>
 #  include <__type_traits/remove_cv.h>
 #  include <__type_traits/remove_cvref.h>
 #  include <__utility/default_three_way_comparator.h>
diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index 2f0127bc7388d..a367753550ddf 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -252,9 +252,9 @@ 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_relocatable.h>
 #  include <__type_traits/is_same.h>
 #  include <__type_traits/is_swappable.h>
-#  include <__type_traits/is_trivially_relocatable.h>
 #  include <__type_traits/maybe_const.h>
 #  include <__type_traits/nat.h>
 #  include <__type_traits/negation.h>
diff --git a/libcxx/include/variant b/libcxx/include/variant
index d9d615478ab80..393aea461af9f 100644
--- a/libcxx/include/variant
+++ b/libcxx/include/variant
@@ -246,12 +246,12 @@ 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_relocatable.h>
 #  include <__type_traits/is_same.h>
 #  include <__type_traits/is_swappable.h>
 #  include <__type_traits/is_trivially_assignable.h>
 #  include <__type_traits/is_trivially_constructible.h>
 #  include <__type_traits/is_trivially_destructible.h>
-#  include <__type_traits/is_trivially_relocatable.h>
 #  include <__type_traits/is_void.h>
 #  include <__type_traits/remove_const.h>
 #  include <__type_traits/remove_cvref.h>
diff --git a/libcxx/test/libcxx/type_traits/is_trivially_relocatable.compile.pass.cpp b/libcxx/test/libcxx/type_traits/is_trivially_relocatable.compile.pass.cpp
index 27961cbab57ba..66b71f7fd366a 100644
--- a/libcxx/test/libcxx/type_traits/is_trivially_relocatable.compile.pass.cpp
+++ b/libcxx/test/libcxx/type_traits/is_trivially_relocatable.compile.pass.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include <__type_traits/is_trivially_relocatable.h>
+#include <__type_traits/is_relocatable.h>
 #include <array>
 #include <deque>
 #include <exception>
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 a1e0d9bb9553d..779d9357832fb 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/common.h
+++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/common.h
@@ -69,22 +69,31 @@ struct ThrowingMoveOnly {
 #endif // TEST_HAS_NO_EXCEPTIONS
 
 struct Tracker {
+  int copy_constructs  = 0;
   int copy_assignments = 0;
+  int move_constructs  = 0;
   int move_assignments = 0;
 };
 
+template <bool Noexcept>
 struct TrackedAssignment {
   Tracker* tracker_;
   TEST_CONSTEXPR_CXX14 explicit TrackedAssignment(Tracker* tracker) : tracker_(tracker) {}
 
-  TrackedAssignment(TrackedAssignment const&) = default;
-  TrackedAssignment(TrackedAssignment&&)      = default;
+  TEST_CONSTEXPR_CXX14 TrackedAssignment(TrackedAssignment const& other) noexcept(Noexcept) : tracker_(other.tracker_) {
+    ++tracker_->copy_constructs;
+  }
+
+  TEST_CONSTEXPR_CXX14 TrackedAssignment(TrackedAssignment&& other) noexcept(Noexcept) : tracker_(other.tracker_) {
+    ++tracker_->move_constructs;
+    other.tracker_ = nullptr;
+  }
 
-  TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment const&) {
+  TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment const&) noexcept(Noexcept) {
     tracker_->copy_assignments++;
     return *this;
   }
-  TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment&&) {
+  TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment&&) noexcept(Noexcept) {
     tracker_->move_assignments++;
     return *this;
   }
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp
index 0c65bd1034bb8..7670faffbe9cf 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
@@ -84,28 +84,6 @@ TEST_CONSTEXPR_CXX20 bool tests() {
   tests<std::allocator, NonTriviallyRelocatable>();
   tests<min_allocator, int>();
   tests<min_allocator, NonTriviallyRelocatable>();
-  return true;
-}
-
-int main(int, char**) {
-  tests();
-#if TEST_STD_VER > 17
-  static_assert(tests());
-#endif
-
-#ifndef TEST_HAS_NO_EXCEPTIONS
-  // Test for LWG2853:
-  // Throws: Nothing unless an exception is thrown by the assignment operator or move assignment operator of T.
-  {
-    Throws arr[] = {1, 2, 3};
-    std::vector<Throws> v(arr, arr + 3);
-    Throws::sThrows = true;
-    v.erase(v.begin());
-    v.erase(--v.end());
-    v.erase(v.begin());
-    assert(v.size() == 0);
-  }
-#endif
 
   // Make sure we satisfy the complexity requirement in terms of the number of times the assignment
   // operator is called.
@@ -115,21 +93,62 @@ int main(int, char**) {
 #ifdef _LIBCPP_VERSION
   {
     Tracker tracker;
-    std::vector<TrackedAssignment> v;
+    std::vector<TrackedAssignment<false>> v;
 
     // Set up the vector with 5 elements.
     for (int i = 0; i != 5; ++i) {
       v.emplace_back(&tracker);
     }
+    tracker = {};
+    // 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_constructs == 0);
     assert(tracker.copy_assignments == 0);
-    assert(tracker.move_assignments == 0);
+    assert(tracker.move_constructs == 0);
+    assert(tracker.move_assignments == 3);
+  }
+  {
+    Tracker tracker;
+    std::vector<TrackedAssignment<true>> v;
 
+    // Set up the vector with 5 elements.
+    for (int i = 0; i != 5; ++i) {
+      v.emplace_back(&tracker);
+    }
+    tracker = {};
     // 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_constructs == 0);
     assert(tracker.copy_assignments == 0);
-    assert(tracker.move_assignments == 3);
+    assert(tracker.move_constructs == 3);
+    assert(tracker.move_assignments == 0);
+  }
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+#if TEST_STD_VER > 17
+  static_assert(tests());
+#endif
+
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  // Test for LWG2853:
+  // Throws: Nothing unless an exception is thrown by the assignment operator or move assignment operator of T.
+  {
+    Throws arr[] = {1, 2, 3};
+    std::vector<Throws> v(arr, arr + 3);
+    Throws::sThrows = true;
+    v.erase(v.begin());
+    v.erase(--v.end());
+    v.erase(v.begin());
+    assert(v.size() == 0);
   }
 #endif
 
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.pass.cpp
index 104dfb4cb07d4..7a16cdd9cb2c8 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
@@ -151,6 +151,51 @@ TEST_CONSTEXPR_CXX20 bool tests() {
   tests<std::allocator, NonTriviallyRelocatable>();
   tests<min_allocator, int>();
   tests<min_allocator, NonTriviallyRelocatable>();
+
+  // 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<false>> v;
+
+    // Set up the vector with 5 elements.
+    for (int i = 0; i != 5; ++i) {
+      v.emplace_back(&tracker);
+    }
+    tracker = {};
+    // 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_constructs == 0);
+    assert(tracker.copy_assignments == 0);
+    assert(tracker.move_constructs == 0);
+    assert(tracker.move_assignments == 2);
+  }
+  {
+    Tracker tracker;
+    std::vector<TrackedAssignment<true>> v;
+
+    // Set up the vector with 5 elements.
+    for (int i = 0; i != 5; ++i) {
+      v.emplace_back(&tracker);
+    }
+    tracker = {};
+    // 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_constructs == 0);
+    assert(tracker.copy_assignments == 0);
+    assert(tracker.move_constructs == 2);
+    assert(tracker.move_assignments == 0);
+  }
+#endif
+
   return true;
 }
 
@@ -196,31 +241,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 042f15e06a90e78edd2f8435866e63567461b9e8 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Tue, 4 Aug 2026 15:10:03 +0200
Subject: [PATCH 2/2] [libc++] Optimize vector::reserve

---
 libcxx/include/CMakeLists.txt    |  2 +-
 libcxx/include/__vector/vector.h | 31 +++++++++++++++++++++++++++----
 2 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index bde6191f94ea6..1d81578a3c56d 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -895,6 +895,7 @@ set(files
   __type_traits/is_polymorphic.h
   __type_traits/is_reference.h
   __type_traits/is_reference_wrapper.h
+  __type_traits/is_relocatable.h
   __type_traits/is_same.h
   __type_traits/is_scalar.h
   __type_traits/is_signed.h
@@ -907,7 +908,6 @@ set(files
   __type_traits/is_trivially_copyable.h
   __type_traits/is_trivially_destructible.h
   __type_traits/is_trivially_lexicographically_comparable.h
-  __type_traits/is_relocatable.h
   __type_traits/is_union.h
   __type_traits/is_unqualified.h
   __type_traits/is_unsigned.h
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index 7413fc66bfe88..9f489c2b6c575 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -36,6 +36,7 @@
 #include <__memory/allocator.h>
 #include <__memory/allocator_traits.h>
 #include <__memory/compressed_pair.h>
+#include <__memory/construct_at.h>
 #include <__memory/noexcept_move_assign_container.h>
 #include <__memory/pointer_traits.h>
 #include <__memory/swap_allocator.h>
@@ -561,13 +562,13 @@ class vector {
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __first, const_iterator __last) {
     _LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "vector::erase(first, last) called with invalid range");
     pointer __pos    = this->__layout_.__begin_ptr() + (__first - begin());
-    auto __raw_pos   = std::__to_address(__pos);
     auto __hole_size = __last - __first;
     if (__first != __last) {
 #ifndef _LIBCPP_CXX03_LANG
       if constexpr (__is_nothrow_relocatable_v<value_type> &&
                     __allocator_has_trivial_move_construct_v<allocator_type, value_type> &&
                     __allocator_has_trivial_destroy_v<allocator_type, value_type>) {
+        auto __raw_pos = std::__to_address(__pos);
         std::__destroy_at(__raw_pos);
         std::__uninitialized_allocator_relocate_forward(
             __layout_.__alloc(), __raw_pos + __hole_size, std::__to_address(__layout_.__end_ptr()), __raw_pos);
@@ -1038,9 +1039,31 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void vector<_Tp, _Allocator>::assign(size_type __n
 
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 void vector<_Tp, _Allocator>::reserve(size_type __n) {
-  if (__n > capacity()) {
-    if (__n > max_size())
-      this->__throw_length_error();
+  if (__n <= capacity())
+    return;
+
+  if (__n > max_size())
+    this->__throw_length_error();
+
+#ifndef _LIBCPP_CXX03_LANG
+  if constexpr (__is_nothrow_relocatable_v<_Tp>) {
+    auto& __alloc     = __layout_.__alloc();
+    auto __allocation = std::__allocate_at_least(__alloc, __n);
+    auto __old_alloc  = __layout_.__begin_ptr();
+    auto __size       = size();
+    auto __old_cap    = capacity();
+
+    __layout_.__set_layout(__allocation.ptr, __size, __allocation.count);
+    std::__uninitialized_allocator_relocate(
+        __alloc,
+        std::__to_address(__old_alloc),
+        std::__to_address(__old_alloc) + __size,
+        std::__to_address(__allocation.ptr));
+    if (__old_alloc)
+      __alloc_traits::deallocate(__alloc, __old_alloc, __old_cap);
+  } else
+#endif
+  {
     _SplitBuffer __v(__n, size(), this->__layout_.__alloc());
     __layout_.__relocate(__v);
   }



More information about the libcxx-commits mailing list