[libcxx-commits] [libcxx] [libc++] Add `__exchange` as a C++11 utility (PR #187953)

Christopher Di Bella via libcxx-commits libcxx-commits at lists.llvm.org
Sun Mar 22 12:17:29 PDT 2026


https://github.com/cjdb created https://github.com/llvm/llvm-project/pull/187953

`std::exchange` is helpful for simplifying move operations because we can couple the resetting component with the move operation. This both halves the number of necessary lines for each move, and also ensures we haven't forgotten to reset the moved-from object. Since `std::exchange` is a C++14 feature, we'll need to use `__exchange` for anything that's from C++11.

This commit also applies `std::__exchange` to a some trivial use-cases to show its utility. Applying this project-wide requires a census over all of our types. It's hoped that this will start a community-wide clean-up that happens over time.

>From 98965d91f9acc7ac3eaf6df65b4095f451788325 Mon Sep 17 00:00:00 2001
From: Christopher Di Bella <cjdb at google.com>
Date: Sun, 22 Mar 2026 10:49:43 -0700
Subject: [PATCH 1/2] [libc++] Add `__exchange` as a C++11 utility

`std::exchange` is helpful for simplifying move operations because we
can couple the resetting component with the move operation. This both
halves the number of necessary lines for each move, and also ensures we
haven't forgotten to reset the moved-from object.

`std::exchange` is a C++14 feature, so we'll need to use `__exchange`
for anything that's from C++11.
---
 libcxx/include/__utility/exchange.h | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/libcxx/include/__utility/exchange.h b/libcxx/include/__utility/exchange.h
index 957e9d0acaa65..ee5913c116545 100644
--- a/libcxx/include/__utility/exchange.h
+++ b/libcxx/include/__utility/exchange.h
@@ -24,14 +24,21 @@ _LIBCPP_PUSH_MACROS
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-#if _LIBCPP_STD_VER >= 14
 template <class _T1, class _T2 = _T1>
-inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _T1 exchange(_T1& __obj, _T2&& __new_value) noexcept(
-    is_nothrow_move_constructible<_T1>::value && is_nothrow_assignable<_T1&, _T2>::value) {
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _T1 __exchange(_T1& __obj, _T2&& __new_value)
+    _NOEXCEPT_(is_nothrow_move_constructible<_T1>::value&& is_nothrow_assignable<_T1&, _T2>::value) {
   _T1 __old_value = std::move(__obj);
   __obj           = std::forward<_T2>(__new_value);
   return __old_value;
 }
+
+#if _LIBCPP_STD_VER >= 14
+template <class _T1, class _T2 = _T1>
+[[nodiscard]] _LIBCPP_ALWAYS_INLINE _LIBCPP_HIDE_FROM_ABI
+_LIBCPP_CONSTEXPR_SINCE_CXX20 _T1 exchange(_T1& __obj, _T2&& __new_value) noexcept(
+    is_nothrow_move_constructible<_T1>::value && is_nothrow_assignable<_T1&, _T2>::value) {
+  return __exchange(__obj, std::forward<_T2>(__new_value));
+}
 #endif // _LIBCPP_STD_VER >= 14
 
 _LIBCPP_END_NAMESPACE_STD

>From 7521c8798ebd31acfa1aae385203e6f49f50ab73 Mon Sep 17 00:00:00 2001
From: Christopher Di Bella <cjdb at google.com>
Date: Sun, 22 Mar 2026 11:52:45 -0700
Subject: [PATCH 2/2] applies `std::__exchange` to some move operations

This is very much non-exhaustive, as a full application requires taking
a census over the whole library. This commit picks a handful of trivial
cases to demonstrate usage, and potentially start a community-wide
clean-up that happens over time.
---
 libcxx/include/__hash_table           |  5 ++---
 libcxx/include/__node_handle          |  9 +++------
 libcxx/include/__split_buffer         |  5 ++---
 libcxx/include/__thread/thread.h      |  5 ++---
 libcxx/include/__vector/vector_bool.h | 26 ++++++++++----------------
 libcxx/include/deque                  | 26 +++++++++-----------------
 libcxx/include/forward_list           |  4 ++--
 libcxx/include/string                 |  4 ++--
 8 files changed, 32 insertions(+), 52 deletions(-)

diff --git a/libcxx/include/__hash_table b/libcxx/include/__hash_table
index ef487fb06dd5e..4b1e729c1d459 100644
--- a/libcxx/include/__hash_table
+++ b/libcxx/include/__hash_table
@@ -41,6 +41,7 @@
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/remove_const.h>
 #include <__type_traits/remove_cvref.h>
+#include <__utility/exchange.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
 #include <__utility/pair.h>
@@ -524,9 +525,7 @@ public:
 
   _LIBCPP_HIDE_FROM_ABI __bucket_list_deallocator(__bucket_list_deallocator&& __x)
       _NOEXCEPT_(is_nothrow_move_constructible<allocator_type>::value)
-      : __size_(std::move(__x.__size_)), __alloc_(std::move(__x.__alloc_)) {
-    __x.size() = 0;
-  }
+      : __size_(std::__exchange(__x.__size_, 0)), __alloc_(std::move(__x.__alloc_)) {}
 
   _LIBCPP_HIDE_FROM_ABI size_type& size() _NOEXCEPT { return __size_; }
   _LIBCPP_HIDE_FROM_ABI size_type size() const _NOEXCEPT { return __size_; }
diff --git a/libcxx/include/__node_handle b/libcxx/include/__node_handle
index b20b0c73a0518..50f8b5bc79107 100644
--- a/libcxx/include/__node_handle
+++ b/libcxx/include/__node_handle
@@ -63,6 +63,7 @@ public:
 #include <__memory/allocator_traits.h>
 #include <__memory/pointer_traits.h>
 #include <__type_traits/is_specialization.h>
+#include <__utility/exchange.h>
 #include <optional>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -120,10 +121,7 @@ public:
   _LIBCPP_HIDE_FROM_ABI __basic_node_handle() = default;
 
   _LIBCPP_HIDE_FROM_ABI __basic_node_handle(__basic_node_handle&& __other) noexcept
-      : __ptr_(__other.__ptr_), __alloc_(std::move(__other.__alloc_)) {
-    __other.__ptr_   = nullptr;
-    __other.__alloc_ = std::nullopt;
-  }
+      : __ptr_(std::exchange(__other.__ptr_, nullptr)), __alloc_(std::exchange(__other.__alloc_, std::nullopt)) {}
 
   _LIBCPP_HIDE_FROM_ABI __basic_node_handle& operator=(__basic_node_handle&& __other) {
     _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(
@@ -133,12 +131,11 @@ public:
         "node_type::operator=(node_type&&)");
 
     __destroy_node_pointer();
-    __ptr_ = __other.__ptr_;
+    __ptr_ = std::exchange(__other.__ptr_, nullptr);
 
     if (__alloc_traits::propagate_on_container_move_assignment::value || __alloc_ == std::nullopt)
       __alloc_ = std::move(__other.__alloc_);
 
-    __other.__ptr_   = nullptr;
     __other.__alloc_ = std::nullopt;
 
     return *this;
diff --git a/libcxx/include/__split_buffer b/libcxx/include/__split_buffer
index 89c398e525998..d911f7eb2b6b4 100644
--- a/libcxx/include/__split_buffer
+++ b/libcxx/include/__split_buffer
@@ -33,6 +33,7 @@
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/is_trivially_destructible.h>
 #include <__type_traits/is_trivially_relocatable.h>
+#include <__utility/exchange.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
 
@@ -727,9 +728,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 __split_buffer<_Tp, _Allocator, _Layout>::~__split
 template <class _Tp, class _Allocator, template <class, class, class> class _Layout>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 __split_buffer<_Tp, _Allocator, _Layout>::__split_buffer(__split_buffer&& __c)
     _NOEXCEPT_(is_nothrow_move_constructible<allocator_type>::value)
-    : __base_type(std::move(__c)) {
-  __c.__reset();
-}
+    : __base_type(std::__exchange(__c, __base_type())) {}
 
 template <class _Tp, class _Allocator, template <class, class, class> class _Layout>
 _LIBCPP_CONSTEXPR_SINCE_CXX20
diff --git a/libcxx/include/__thread/thread.h b/libcxx/include/__thread/thread.h
index b2f51aa816c10..064855eb8717f 100644
--- a/libcxx/include/__thread/thread.h
+++ b/libcxx/include/__thread/thread.h
@@ -236,13 +236,12 @@ class _LIBCPP_EXPORTED_FROM_ABI thread {
 #  endif
   ~thread();
 
-  _LIBCPP_HIDE_FROM_ABI thread(thread&& __t) _NOEXCEPT : __t_(__t.__t_) { __t.__t_ = _LIBCPP_NULL_THREAD; }
+  _LIBCPP_HIDE_FROM_ABI thread(thread&& __t) _NOEXCEPT : __t_(std::__exchange(__t.__t_, _LIBCPP_NULL_THREAD)) {}
 
   _LIBCPP_HIDE_FROM_ABI thread& operator=(thread&& __t) _NOEXCEPT {
     if (!__libcpp_thread_isnull(&__t_))
       terminate();
-    __t_     = __t.__t_;
-    __t.__t_ = _LIBCPP_NULL_THREAD;
+    __t_ = std::__exchange(__t.__t_, _LIBCPP_NULL_THREAD);
     return *this;
   }
 
diff --git a/libcxx/include/__vector/vector_bool.h b/libcxx/include/__vector/vector_bool.h
index f81fcd92a7e49..f73e086478a70 100644
--- a/libcxx/include/__vector/vector_bool.h
+++ b/libcxx/include/__vector/vector_bool.h
@@ -45,6 +45,7 @@
 #include <__type_traits/is_nothrow_constructible.h>
 #include <__type_traits/type_identity.h>
 #include <__utility/exception_guard.h>
+#include <__utility/exchange.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
 #include <__utility/swap.h>
@@ -740,13 +741,10 @@ inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector<bool, _Allocat
 #else
     _NOEXCEPT_(is_nothrow_move_constructible<allocator_type>::value)
 #endif
-    : __begin_(__v.__begin_),
-      __size_(__v.__size_),
-      __cap_(__v.__cap_),
+    : __begin_(std::__exchange(__v.__begin_, nullptr)),
+      __size_(std::__exchange(__v.__size_, 0)),
+      __cap_(std::__exchange(__v.__cap_, 0)),
       __alloc_(std::move(__v.__alloc_)) {
-  __v.__begin_ = nullptr;
-  __v.__size_  = 0;
-  __v.__cap_   = 0;
 }
 
 template <class _Allocator>
@@ -754,11 +752,9 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20
 vector<bool, _Allocator>::vector(vector&& __v, const __type_identity_t<allocator_type>& __a)
     : __begin_(nullptr), __size_(0), __cap_(0), __alloc_(__a) {
   if (__a == allocator_type(__v.__alloc_)) {
-    this->__begin_ = __v.__begin_;
-    this->__size_  = __v.__size_;
-    this->__cap_   = __v.__cap_;
-    __v.__begin_   = nullptr;
-    __v.__cap_ = __v.__size_ = 0;
+    __begin_ = std::__exchange(__v.__begin_, nullptr);
+    __size_  = std::__exchange(__v.__size_, 0);
+    __cap_   = std::__exchange(__v.__cap_, 0);
   } else if (__v.size() > 0) {
     __vallocate(__v.size());
     __size_ = __v.__size_;
@@ -787,11 +783,9 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void vector<bool, _Allocator>::__move_assign(vecto
     _NOEXCEPT_(is_nothrow_move_assignable<allocator_type>::value) {
   __vdeallocate();
   __move_assign_alloc(__c);
-  this->__begin_ = __c.__begin_;
-  this->__size_  = __c.__size_;
-  this->__cap_   = __c.__cap_;
-  __c.__begin_   = nullptr;
-  __c.__cap_ = __c.__size_ = 0;
+  __begin_ = std::__exchange(__c.__begin_, nullptr);
+  __size_  = std::__exchange(__c.__size_, 0);
+  __cap_   = std::__exchange(__c.__cap_, 0);
 }
 
 template <class _Allocator>
diff --git a/libcxx/include/deque b/libcxx/include/deque
index c8c6889f1a165..ab2d2d3a637a4 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -233,6 +233,7 @@ template <class T, class Allocator, class Predicate>
 #  include <__type_traits/is_trivially_relocatable.h>
 #  include <__type_traits/type_identity.h>
 #  include <__utility/exception_guard.h>
+#  include <__utility/exchange.h>
 #  include <__utility/forward.h>
 #  include <__utility/move.h>
 #  include <__utility/pair.h>
@@ -928,10 +929,9 @@ public:
       _NOEXCEPT_(__alloc_traits::propagate_on_container_move_assignment::value&&
                      is_nothrow_move_assignable<allocator_type>::value) {
     __map_   = std::move(__c.__map_);
-    __start_ = __c.__start_;
-    __size() = __c.size();
+    __start_ = std::__exchange(__c.__start_, nullptr);
+    __size() = std::__exchange(__c.size(), 0);
     __move_assign_alloc(__c);
-    __c.__start_ = __c.__size() = 0;
   }
 
   _LIBCPP_HIDE_FROM_ABI static size_type __recommend_blocks(size_type __n) {
@@ -1392,26 +1392,18 @@ deque<_Tp, _Allocator>::deque(initializer_list<value_type> __il, const allocator
 template <class _Tp, class _Allocator>
 inline deque<_Tp, _Allocator>::deque(deque&& __c) noexcept(is_nothrow_move_constructible<allocator_type>::value)
     : __map_(std::move(__c.__map_)),
-      __start_(std::move(__c.__start_)),
-      __size_(std::move(__c.__size_)),
-      __alloc_(std::move(__c.__alloc_)) {
-  __c.__start_ = 0;
-  __c.__size() = 0;
-}
+      __start_(std::__exchange(__c.__start_, 0)),
+      __size_(std::__exchange(__c.__size_, 0)),
+      __alloc_(std::move(__c.__alloc_)) {}
 
 template <class _Tp, class _Allocator>
 inline deque<_Tp, _Allocator>::deque(deque&& __c, const __type_identity_t<allocator_type>& __a)
     : __map_(std::move(__c.__map_), __pointer_allocator(__a)),
-      __start_(std::move(__c.__start_)),
-      __size_(std::move(__c.__size_)),
+      __start_(std::__exchange(__c.__start_, 0)),
+      __size_(std::__exchange(__c.__size_, 0)),
       __alloc_(__a) {
-  if (__a == __c.__alloc()) {
-    __c.__start_ = 0;
-    __c.__size() = 0;
-  } else {
+  if (__a != __c.__alloc()) {
     __map_.clear();
-    __start_ = 0;
-    __size() = 0;
     typedef move_iterator<iterator> _Ip;
     assign(_Ip(__c.begin()), _Ip(__c.end()));
   }
diff --git a/libcxx/include/forward_list b/libcxx/include/forward_list
index 56c45d0d46575..35afb7301f480 100644
--- a/libcxx/include/forward_list
+++ b/libcxx/include/forward_list
@@ -234,6 +234,7 @@ template <class T, class Allocator, class Predicate>
 #  include <__type_traits/remove_cv.h>
 #  include <__type_traits/type_identity.h>
 #  include <__utility/exception_guard.h>
+#  include <__utility/exchange.h>
 #  include <__utility/forward.h>
 #  include <__utility/move.h>
 #  include <__utility/swap.h>
@@ -1022,8 +1023,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX26 void forward_list<_Tp, _Alloc>::__move_assign(forw
     _NOEXCEPT_(is_nothrow_move_assignable<allocator_type>::value) {
   clear();
   __base::__move_assign_alloc(__x);
-  __base::__before_begin()->__next_ = __x.__before_begin()->__next_;
-  __x.__before_begin()->__next_     = nullptr;
+  __base::__before_begin()->__next_ = std::__exchange(__x.__before_begin()->__next_, nullptr);
 }
 
 template <class _Tp, class _Alloc>
diff --git a/libcxx/include/string b/libcxx/include/string
index 0c8767df2cdd2..002ce63ce5af9 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -644,6 +644,7 @@ basic_string<char32_t> operator""s( const char32_t *str, size_t len );
 #  include <__type_traits/remove_cvref.h>
 #  include <__utility/default_three_way_comparator.h>
 #  include <__utility/exception_guard.h>
+#  include <__utility/exchange.h>
 #  include <__utility/forward.h>
 #  include <__utility/is_pointer_in_range.h>
 #  include <__utility/move.h>
@@ -1490,8 +1491,7 @@ public:
     size_type __old_sz = __str.size();
     if (!__str.__is_long())
       __str.__annotate_delete();
-    __rep_       = __str.__rep_;
-    __str.__rep_ = __rep();
+    __rep_ = std::__exchange(__str.__rep_, __rep());
     __str.__annotate_new(0);
 
     _Traits::move(data(), data() + __pos, __len);



More information about the libcxx-commits mailing list