[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