[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:
parent
adfb3be9d0
commit
b5fa9eee67
4 changed files with 136 additions and 4 deletions
|
|
@ -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>`__",""
|
||||
|
|
|
|||
|
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue