[libcxx-commits] [libcxx] [libc++] Add `__exchange` as a C++11 utility (PR #187953)
via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Mar 22 12:17:59 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Christopher Di Bella (cjdb)
<details>
<summary>Changes</summary>
`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.
---
Full diff: https://github.com/llvm/llvm-project/pull/187953.diff
9 Files Affected:
- (modified) libcxx/include/__hash_table (+2-3)
- (modified) libcxx/include/__node_handle (+3-6)
- (modified) libcxx/include/__split_buffer (+2-3)
- (modified) libcxx/include/__thread/thread.h (+2-3)
- (modified) libcxx/include/__utility/exchange.h (+10-3)
- (modified) libcxx/include/__vector/vector_bool.h (+10-16)
- (modified) libcxx/include/deque (+9-17)
- (modified) libcxx/include/forward_list (+2-2)
- (modified) libcxx/include/string (+2-2)
``````````diff
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/__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
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);
``````````
</details>
https://github.com/llvm/llvm-project/pull/187953
More information about the libcxx-commits
mailing list