[libcxx-commits] [libcxx] [libc++] LWG3918: copy elision in `std::uninitialized_move/_n` (PR #207692)

via libcxx-commits libcxx-commits at lists.llvm.org
Mon Jul 6 03:27:56 PDT 2026


https://github.com/inquisitivecrystal updated https://github.com/llvm/llvm-project/pull/207692

>From 8551a998c0e6da70f2168cd02b1249b28de12adf Mon Sep 17 00:00:00 2001
From: inquisitivecrystal
 <22333129+inquisitivecrystal at users.noreply.github.com>
Date: Sun, 5 Jul 2026 01:27:35 -0700
Subject: [PATCH 1/3] Ensure copy-elision for uninitialized_move(_n)

This implements the resolution for LWG issues 3918 and 4452.
---
 libcxx/include/__memory/uninitialized_algorithms.h | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

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);

>From dad12baeec8d251e68ee97a73f237588da3518df Mon Sep 17 00:00:00 2001
From: inquisitivecrystal
 <22333129+inquisitivecrystal at users.noreply.github.com>
Date: Mon, 6 Jul 2026 02:38:47 -0700
Subject: [PATCH 2/3] Test changes to uninitialized_move(_n)

This tests that copies are elided for prvalues. It includes a
constexpr test in C++26.
---
 .../uninitialized_move.pass.cpp               | 62 ++++++++++++++++++
 .../uninitialized_move_n.pass.cpp             | 65 +++++++++++++++++++
 2 files changed, 127 insertions(+)

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;

>From 0a53270b60cc74d91306e400dee5163777b9e180 Mon Sep 17 00:00:00 2001
From: inquisitivecrystal
 <22333129+inquisitivecrystal at users.noreply.github.com>
Date: Mon, 6 Jul 2026 02:47:30 -0700
Subject: [PATCH 3/3] Mark LWG issues 3918 and 4452 as complete

---
 libcxx/docs/Status/Cxx26Issues.csv | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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>`__",""



More information about the libcxx-commits mailing list