[libcxx-commits] [libcxx] b5fa9ee - [libc++] Implement LWG3918: copy elision in `std::uninitialized_move/_n` (#207692)
via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Jul 8 05:07:06 PDT 2026
Author: inquisitivecrystal
Date: 2026-07-08T20:06:57+08:00
New Revision: b5fa9eee6798b678fc7cb5f2b42a977932b708f9
URL: https://github.com/llvm/llvm-project/commit/b5fa9eee6798b678fc7cb5f2b42a977932b708f9
DIFF: https://github.com/llvm/llvm-project/commit/b5fa9eee6798b678fc7cb5f2b42a977932b708f9.diff
LOG: [libc++] Implement LWG3918: copy elision in `std::uninitialized_move/_n` (#207692)
This implements [LWG3918](https://wg21.link/LWG3918), which guarantees
copy elision for rvalues in `std::uninitialized_move/_n`. It also
implements [LWG4452](https://wg21.link/LWG4452), a minor correction that
makes the helper added by LWG3918 constexpr.
This additionally fixes a bug in `std::uninitialized_move/_n` where they
could create and then access dangling references. The previous
implementation used the following lambda as an implementation detail:
```c++
[](auto&& __iter) -> decltype(auto) { return std::move(*__iter); }
```
When `__iter` is a prvalue, this creates a temporary object within the
lambda's body and `std::move` then returns a reference to that object.
The reference dangles as soon as control leaves the lambda. This
behavior was not permitted by the standard even before LWG3918.
The new implementation fixes the bug by using a helper,
`std::__deref_move(__iter)`, which returns `*__iter` directly when
`*__iter` is already an rvalue.
Resolves #118339.
Resolves #171416.
Added:
Modified:
libcxx/docs/Status/Cxx26Issues.csv
libcxx/include/__memory/uninitialized_algorithms.h
libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move.pass.cpp
libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move_n.pass.cpp
Removed:
################################################################################
diff --git a/libcxx/docs/Status/Cxx26Issues.csv b/libcxx/docs/Status/Cxx26Issues.csv
index 5de35c96d4c0a..e8edd9eefe1b1 100644
--- a/libcxx/docs/Status/Cxx26Issues.csv
+++ b/libcxx/docs/Status/Cxx26Issues.csv
@@ -81,7 +81,7 @@
"`LWG3886 <https://wg21.link/LWG3886>`__","Monad mo' problems","2024-11 (Wrocław)","|Complete|","22","`#118336 <https://github.com/llvm/llvm-project/issues/118336>`__",""
"`LWG3899 <https://wg21.link/LWG3899>`__","``co_yield``\ing elements of an lvalue generator is unnecessarily inefficient","2024-11 (Wrocław)","","","`#118337 <https://github.com/llvm/llvm-project/issues/118337>`__",""
"`LWG3900 <https://wg21.link/LWG3900>`__","The ``allocator_arg_t`` overloads of ``generator::promise_type::operator new`` should not be constrained","2024-11 (Wrocław)","","","`#118338 <https://github.com/llvm/llvm-project/issues/118338>`__",""
-"`LWG3918 <https://wg21.link/LWG3918>`__","``std::uninitialized_move/_n`` and guaranteed copy elision","2024-11 (Wrocław)","","","`#118339 <https://github.com/llvm/llvm-project/issues/118339>`__",""
+"`LWG3918 <https://wg21.link/LWG3918>`__","``std::uninitialized_move/_n`` and guaranteed copy elision","2024-11 (Wrocław)","|Complete|","23","`#118339 <https://github.com/llvm/llvm-project/issues/118339>`__",""
"`LWG4014 <https://wg21.link/LWG4014>`__","LWG 3809 changes behavior of some existing ``std::subtract_with_carry_engine code``","2024-11 (Wrocław)","","","`#118340 <https://github.com/llvm/llvm-project/issues/118340>`__",""
"`LWG4024 <https://wg21.link/LWG4024>`__","Underspecified destruction of objects created in ``std::make_shared_for_overwrite``/``std::allocate_shared_for_overwrite``","2024-11 (Wrocław)","|Complete|","16","`#118341 <https://github.com/llvm/llvm-project/issues/118341>`__",""
"`LWG4027 <https://wg21.link/LWG4027>`__","``possibly-const-range`` should prefer returning ``const R&``","2024-11 (Wrocław)","","","`#118342 <https://github.com/llvm/llvm-project/issues/118342>`__",""
@@ -250,7 +250,7 @@
"`LWG4449 <https://wg21.link/LWG4449>`__","``define_aggregate`` members must be public","2025-11 (Kona)","","","`#171412 <https://github.com/llvm/llvm-project/issues/171412>`__",""
"`LWG4450 <https://wg21.link/LWG4450>`__","``std::atomic_ref<T>::store_key`` should be disabled for const ``T``","2025-11 (Kona)","","","`#171414 <https://github.com/llvm/llvm-project/issues/171414>`__",""
"`LWG4451 <https://wg21.link/LWG4451>`__","``make_shared`` should not refer to a type ``U[N]`` for runtime N","2025-11 (Kona)","","","`#171415 <https://github.com/llvm/llvm-project/issues/171415>`__",""
-"`LWG4452 <https://wg21.link/LWG4452>`__","Make *deref-move* constexpr","2025-11 (Kona)","","","`#171416 <https://github.com/llvm/llvm-project/issues/171416>`__",""
+"`LWG4452 <https://wg21.link/LWG4452>`__","Make *deref-move* constexpr","2025-11 (Kona)","|Complete|","23","`#171416 <https://github.com/llvm/llvm-project/issues/171416>`__",""
"`LWG4455 <https://wg21.link/LWG4455>`__","Add missing constraint to ``basic-sender::get_completion_signatures`` definition","2025-11 (Kona)","","","`#171417 <https://github.com/llvm/llvm-project/issues/171417>`__",""
"`LWG4456 <https://wg21.link/LWG4456>`__","Decay ``Data`` and ``Child`` in ``make-sender``","2025-11 (Kona)","","","`#171418 <https://github.com/llvm/llvm-project/issues/171418>`__",""
"`LWG4459 <https://wg21.link/LWG4459>`__","Protect ``get_completion_signatures`` fold expression from overloaded commas","2025-11 (Kona)","","","`#171419 <https://github.com/llvm/llvm-project/issues/171419>`__",""
diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index d4cd49a1deb6e..f184b2b9dd925 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -25,6 +25,7 @@
#include <__memory/pointer_traits.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/is_constant_evaluated.h>
+#include <__type_traits/is_reference.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_trivially_assignable.h>
#include <__type_traits/is_trivially_constructible.h>
@@ -141,6 +142,14 @@ uninitialized_fill_n(_ForwardIterator __first, _Size __n, const _Tp& __x) {
#if _LIBCPP_STD_VER >= 17
+template <class _Iter>
+_LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) __deref_move(_Iter& __it) {
+ if constexpr (is_lvalue_reference_v<decltype(*__it)>)
+ return std::move(*__it);
+ else
+ return *__it;
+}
+
// uninitialized_default_construct
template <class _ValueType, class _ForwardIterator, class _Sentinel>
@@ -253,7 +262,7 @@ template <class _InputIterator, class _ForwardIterator>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 _ForwardIterator
uninitialized_move(_InputIterator __ifirst, _InputIterator __ilast, _ForwardIterator __ofirst) {
using _ValueType = typename iterator_traits<_ForwardIterator>::value_type;
- auto __iter_move = [](auto&& __iter) -> decltype(auto) { return std::move(*__iter); };
+ auto __iter_move = [](auto&& __iter) -> decltype(auto) { return std::__deref_move(__iter); };
auto __result = std::__uninitialized_move<_ValueType>(
std::move(__ifirst), std::move(__ilast), std::move(__ofirst), __always_false(), __iter_move);
@@ -284,7 +293,7 @@ template <class _InputIterator, class _Size, class _ForwardIterator>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX26 pair<_InputIterator, _ForwardIterator>
uninitialized_move_n(_InputIterator __ifirst, _Size __n, _ForwardIterator __ofirst) {
using _ValueType = typename iterator_traits<_ForwardIterator>::value_type;
- auto __iter_move = [](auto&& __iter) -> decltype(auto) { return std::move(*__iter); };
+ auto __iter_move = [](auto&& __iter) -> decltype(auto) { return std::__deref_move(__iter); };
auto __result = std::__uninitialized_move_n<_ValueType>(
std::move(__ifirst), __n, std::move(__ofirst), __always_false(), __iter_move);
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move.pass.cpp
index 13fef7a972e95..ac348ac88164d 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move.pass.cpp
@@ -56,6 +56,38 @@ int ThrowsCounted::count = 0;
int ThrowsCounted::constructed = 0;
int ThrowsCounted::throw_after = 0;
+struct NoMoveNoCopy {
+ constexpr explicit NoMoveNoCopy(int x) : value(x) {}
+ NoMoveNoCopy(const NoMoveNoCopy&) = delete;
+ friend void operator&(NoMoveNoCopy) = delete;
+ int value;
+};
+
+class PrvalueIterator {
+public:
+ using iterator_category = std::input_iterator_tag;
+ using
diff erence_type = std::ptr
diff _t;
+ using reference = NoMoveNoCopy;
+ using pointer = void;
+ using value_type = NoMoveNoCopy;
+
+ PrvalueIterator() = delete;
+ constexpr explicit PrvalueIterator(const int* ptr) : ptr_(ptr) {}
+
+ constexpr NoMoveNoCopy operator*() const { return NoMoveNoCopy(*ptr_); }
+
+ constexpr PrvalueIterator& operator++() {
+ ++ptr_;
+ return *this;
+ }
+
+ friend constexpr bool operator==(PrvalueIterator a, PrvalueIterator b) { return a.ptr_ == b.ptr_; }
+ friend constexpr bool operator!=(PrvalueIterator a, PrvalueIterator b) { return a.ptr_ != b.ptr_; }
+
+private:
+ const int* ptr_;
+};
+
TEST_CONSTEXPR_CXX26 bool test() {
const int n = 3;
MoveOnly in[n] = {1, 2, 3};
@@ -128,9 +160,36 @@ void test_counted()
assert(Counted::count == 0);
}
+TEST_CONSTEXPR_CXX26 bool test_copy_elision() {
+ using It = PrvalueIterator;
+ using FIt = forward_iterator<NoMoveNoCopy*>;
+ const int N = 5;
+ int values[N] = {1, 2, 3, 4, 5};
+ std::allocator<NoMoveNoCopy> alloc;
+ NoMoveNoCopy* p = alloc.allocate(N);
+ auto ret = std::uninitialized_move(It(values), It(values + 1), FIt(p));
+ assert(ret == FIt(p + 1));
+ assert(p[0].value == 1);
+ assert(values[0] == 1);
+ ret = std::uninitialized_move(It(values + 1), It(values + N), FIt(p + 1));
+ assert(p[1].value == 2);
+ assert(p[2].value == 3);
+ assert(p[3].value == 4);
+ assert(p[4].value == 5);
+ assert(values[1] == 2);
+ assert(values[2] == 3);
+ assert(values[3] == 4);
+ assert(values[4] == 5);
+ std::destroy(p, p + N);
+ alloc.deallocate(p, N);
+
+ return true;
+}
+
int main(int, char**) {
test_counted();
test_ctor_throws();
+ test_copy_elision();
// Test with an iterator that overloads operator== and operator!= as the input and output iterators
{
@@ -164,6 +223,7 @@ int main(int, char**) {
test();
#if TEST_STD_VER >= 26
static_assert(test());
+ static_assert(test_copy_elision());
#endif
return 0;
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move_n.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move_n.pass.cpp
index 785cfb406efd9..2483a5519d51e 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move_n.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move_n.pass.cpp
@@ -56,6 +56,38 @@ int ThrowsCounted::count = 0;
int ThrowsCounted::constructed = 0;
int ThrowsCounted::throw_after = 0;
+struct NoMoveNoCopy {
+ constexpr explicit NoMoveNoCopy(int x) : value(x) {}
+ NoMoveNoCopy(const NoMoveNoCopy&) = delete;
+ friend void operator&(NoMoveNoCopy) = delete;
+ int value;
+};
+
+class PrvalueIterator {
+public:
+ using iterator_category = std::input_iterator_tag;
+ using
diff erence_type = std::ptr
diff _t;
+ using reference = NoMoveNoCopy;
+ using pointer = void;
+ using value_type = NoMoveNoCopy;
+
+ PrvalueIterator() = delete;
+ constexpr explicit PrvalueIterator(const int* ptr) : ptr_(ptr) {}
+
+ constexpr NoMoveNoCopy operator*() const { return NoMoveNoCopy(*ptr_); }
+
+ constexpr PrvalueIterator& operator++() {
+ ++ptr_;
+ return *this;
+ }
+
+ friend constexpr bool operator==(PrvalueIterator a, PrvalueIterator b) { return a.ptr_ == b.ptr_; }
+ friend constexpr bool operator!=(PrvalueIterator a, PrvalueIterator b) { return a.ptr_ != b.ptr_; }
+
+private:
+ const int* ptr_;
+};
+
TEST_CONSTEXPR_CXX26 bool test() {
const int n = 3;
MoveOnly in[n] = {1, 2, 3};
@@ -99,6 +131,35 @@ void test_ctor_throws()
#endif
}
+TEST_CONSTEXPR_CXX26 bool test_copy_elision() {
+ using It = PrvalueIterator;
+ using FIt = forward_iterator<NoMoveNoCopy*>;
+ const int N = 5;
+ int values[N] = {1, 2, 3, 4, 5};
+ std::allocator<NoMoveNoCopy> alloc;
+ NoMoveNoCopy* p = alloc.allocate(N);
+ auto ret = std::uninitialized_move_n(It(values), 1, FIt(p));
+ assert(ret.first == It(values + 1));
+ assert(ret.second == FIt(p + 1));
+ assert(p[0].value == 1);
+ assert(values[0] == 1);
+ ret = std::uninitialized_move_n(It(values + 1), N - 1, FIt(p + 1));
+ assert(ret.first == It(values + N));
+ assert(ret.second == FIt(p + N));
+ assert(p[1].value == 2);
+ assert(p[2].value == 3);
+ assert(p[3].value == 4);
+ assert(p[4].value == 5);
+ assert(values[1] == 2);
+ assert(values[2] == 3);
+ assert(values[3] == 4);
+ assert(values[4] == 5);
+ std::destroy(p, p + N);
+ alloc.deallocate(p, N);
+
+ return true;
+}
+
void test_counted()
{
using It = cpp17_input_iterator<int*>;
@@ -135,6 +196,7 @@ int main(int, char**)
{
test_counted();
test_ctor_throws();
+ test_copy_elision();
// Test with an iterator that overloads operator== and operator!= as the input and output iterators
{
@@ -168,6 +230,7 @@ int main(int, char**)
test();
#if TEST_STD_VER >= 26
static_assert(test());
+ static_assert(test_copy_elision());
#endif
return 0;
More information about the libcxx-commits
mailing list