[libcxx-commits] [libcxx] 8af8a60 - [libc++] Add `__exchange` as a C++11 utility (#187953)
via libcxx-commits
libcxx-commits at lists.llvm.org
Mon May 4 12:51:04 PDT 2026
Author: Christopher Di Bella
Date: 2026-05-05T03:51:00+08:00
New Revision: 8af8a60d544afaeac4834bb78f6b55bc890fb5fd
URL: https://github.com/llvm/llvm-project/commit/8af8a60d544afaeac4834bb78f6b55bc890fb5fd
DIFF: https://github.com/llvm/llvm-project/commit/8af8a60d544afaeac4834bb78f6b55bc890fb5fd.diff
LOG: [libc++] Add `__exchange` as a C++11 utility (#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.
Added:
Modified:
libcxx/include/__hash_table
libcxx/include/__node_handle
libcxx/include/__thread/thread.h
libcxx/include/__utility/exchange.h
libcxx/include/__vector/vector_bool.h
libcxx/include/forward_list
libcxx/include/string
libcxx/test/std/ranges/range.adaptors/range.chunk.by/begin.pass.cpp
Removed:
################################################################################
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..7342cbb5e37f6 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_thread_t())) {}
_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_thread_t());
return *this;
}
diff --git a/libcxx/include/__utility/exchange.h b/libcxx/include/__utility/exchange.h
index 957e9d0acaa65..368818c5541bd 100644
--- a/libcxx/include/__utility/exchange.h
+++ b/libcxx/include/__utility/exchange.h
@@ -10,6 +10,9 @@
#define _LIBCPP___UTILITY_EXCHANGE_H
#include <__config>
+#include <__type_traits/enable_if.h>
+#include <__type_traits/is_assignable.h>
+#include <__type_traits/is_constructible.h>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__utility/forward.h>
@@ -24,14 +27,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_ALWAYS_INLINE _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_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 std::__exchange(__obj, std::forward<_T2>(__new_value));
+}
#endif // _LIBCPP_STD_VER >= 14
_LIBCPP_END_NAMESPACE_STD
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..72250d2f2f9ed 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
@@ -100,12 +100,12 @@ constexpr bool test() {
// Make sure we do not make a copy of the predicate when we call begin()
// (we should be passing it to ranges::adjacent_find using std::ref)
{
- bool moved = false, copied = false;
+ bool moved = false;
+ bool copied = false;
Range range(buff, buff + 2);
std::ranges::chunk_by_view view(range, TrackingPred(&moved, &copied));
- std::exchange(moved, false);
[[maybe_unused]] auto it = view.begin();
- assert(!moved);
+ assert(moved);
assert(!copied);
}
More information about the libcxx-commits
mailing list