[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.
This commit is contained in:
inquisitivecrystal 2026-07-08 05:06:57 -07:00 committed by GitHub
parent adfb3be9d0
commit b5fa9eee67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 136 additions and 4 deletions

View file

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

1 Issue # Issue Name Meeting Status First released version GitHub issue Notes
81 `LWG3886 <https://wg21.link/LWG3886>`__ Monad mo' problems 2024-11 (Wrocław) |Complete| 22 `#118336 <https://github.com/llvm/llvm-project/issues/118336>`__
82 `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>`__
83 `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>`__
84 `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>`__
85 `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>`__
86 `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>`__
87 `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 `LWG4449 <https://wg21.link/LWG4449>`__ ``define_aggregate`` members must be public 2025-11 (Kona) `#171412 <https://github.com/llvm/llvm-project/issues/171412>`__
251 `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>`__
252 `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>`__
253 `LWG4452 <https://wg21.link/LWG4452>`__ Make *deref-move* constexpr 2025-11 (Kona) |Complete| 23 `#171416 <https://github.com/llvm/llvm-project/issues/171416>`__
254 `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>`__
255 `LWG4456 <https://wg21.link/LWG4456>`__ Decay ``Data`` and ``Child`` in ``make-sender`` 2025-11 (Kona) `#171418 <https://github.com/llvm/llvm-project/issues/171418>`__
256 `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>`__

View file

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

View file

@ -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 difference_type = std::ptrdiff_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;

View file

@ -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 difference_type = std::ptrdiff_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;