[libcxx-commits] [libcxx] [libc++] LWG3918: `std::uninitialized_move/_n` and guaranteed copy elision (PR #207692)
via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Jul 6 03:12:54 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: inquisitivecrystal (inquisitivecrystal)
<details>
<summary>Changes</summary>
This implements [LWG3918](wg21.link/LWG3918), which guarantees copy-elision for rvalues in `std::uninitialized_move/_n`. It also implements [LWG4452](wg21.link/LWG4452), a minor correction that makes the helper added by LWG3918 constexpr.
Interestingly, due to an implementation oversight, libc++'s `std::uninitialized_move/_n` didn't work *at all* for prvalues before this change. The previous implementation used a lambda `[](auto&& __iter) -> decltype(auto) { return std::move(*__iter); };`. If I'm understanding correctly, when the iterator's reference type is a prvalue, `*__iter` materializes a temporary and the lambda then returns a dangling reference to that temporary. The standard text doesn't have a lambda, so it would have allowed prvalues even before LWG3918, just with an extra copy. My tests cover this case and show that it now works correctly.
Resolves #<!-- -->118339.
Resolves #<!-- -->171416.
---
Full diff: https://github.com/llvm/llvm-project/pull/207692.diff
4 Files Affected:
- (modified) libcxx/docs/Status/Cxx26Issues.csv (+2-2)
- (modified) libcxx/include/__memory/uninitialized_algorithms.h (+11-2)
- (modified) libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move.pass.cpp (+62)
- (modified) libcxx/test/std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_move_n.pass.cpp (+65)
``````````diff
diff --git a/libcxx/docs/Status/Cxx26Issues.csv b/libcxx/docs/Status/Cxx26Issues.csv
index 3f2a446f7e1f3..cba4246e05361 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 45c8b71459eef..f5035ea2e21b9 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -28,6 +28,7 @@
#include <__type_traits/extent.h>
#include <__type_traits/is_array.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>
@@ -145,6 +146,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>
@@ -257,7 +266,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);
@@ -288,7 +297,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..a248e25e8d966 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,40 @@ int ThrowsCounted::count = 0;
int ThrowsCounted::constructed = 0;
int ThrowsCounted::throw_after = 0;
+struct NoMoveNoCopy {
+ constexpr explicit NoMoveNoCopy(int x) : value(x) {}
+ NoMoveNoCopy(NoMoveNoCopy const&) { assert(false); }
+ NoMoveNoCopy(NoMoveNoCopy const&&) { assert(false); }
+
+ friend void operator&(NoMoveNoCopy) = delete;
+ int value;
+};
+
+class PrvalueIterator {
+public:
+ using iterator_category = std::input_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using reference = NoMoveNoCopy;
+ using pointer = void;
+ using value_type = NoMoveNoCopy;
+
+ PrvalueIterator() = delete;
+ constexpr 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 +162,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 +225,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..ab0b917972963 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,40 @@ int ThrowsCounted::count = 0;
int ThrowsCounted::constructed = 0;
int ThrowsCounted::throw_after = 0;
+struct NoMoveNoCopy {
+ constexpr explicit NoMoveNoCopy(int x) : value(x) {}
+ NoMoveNoCopy(NoMoveNoCopy const&) { assert(false); }
+ NoMoveNoCopy(NoMoveNoCopy const&&) { assert(false); }
+
+ friend void operator&(NoMoveNoCopy) = delete;
+ int value;
+};
+
+class PrvalueIterator {
+public:
+ using iterator_category = std::input_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using reference = NoMoveNoCopy;
+ using pointer = void;
+ using value_type = NoMoveNoCopy;
+
+ PrvalueIterator() = delete;
+ constexpr 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 +133,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 +198,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 +232,7 @@ int main(int, char**)
test();
#if TEST_STD_VER >= 26
static_assert(test());
+ static_assert(test_copy_elision());
#endif
return 0;
``````````
</details>
https://github.com/llvm/llvm-project/pull/207692
More information about the libcxx-commits
mailing list