[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