[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
Mon Mar 23 00:20:05 PDT 2026
https://github.com/cjdb updated https://github.com/llvm/llvm-project/pull/187953
>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 dbe2ed16595aa7b7ab1605b4caedc52d710599da 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/__thread/thread.h | 5 ++--
libcxx/include/__vector/vector_bool.h | 26 +++++++------------
libcxx/include/forward_list | 4 +--
libcxx/include/string | 4 +--
.../range.chunk.by/begin.pass.cpp | 2 +-
7 files changed, 22 insertions(+), 33 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/__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/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);
diff --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/begin.pass.cpp
index 2fa9218272fee..b52181c68f899 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/begin.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/begin.pass.cpp
@@ -103,7 +103,7 @@ constexpr bool test() {
bool moved = false, copied = false;
Range range(buff, buff + 2);
std::ranges::chunk_by_view view(range, TrackingPred(&moved, &copied));
- std::exchange(moved, false);
+ moved = false;
[[maybe_unused]] auto it = view.begin();
assert(!moved);
assert(!copied);
More information about the libcxx-commits
mailing list