Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement LWG-3918 std::uninitialized_move/_n and guaranteed copy elision #5135

Merged
merged 8 commits into from
Dec 5, 2024
5 changes: 0 additions & 5 deletions stl/inc/execution
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,6 @@ template <>
struct is_execution_policy<execution::unsequenced_policy> : true_type {};
#endif // _HAS_CXX20

template <class _Ty, class _FwdIt>
void _Construct_in_place_by_deref(_Ty& _Val, const _FwdIt& _Iter) {
::new (static_cast<void*>(_STD addressof(_Val))) _Ty(*_Iter);
}

template <class _Ty, class _UnaryOp, class _FwdIt>
void _Construct_in_place_by_transform_deref(_Ty& _Val, _UnaryOp _Transform_op, const _FwdIt& _Iter) {
::new (static_cast<void*>(_STD addressof(_Val))) _Ty(_Transform_op(*_Iter));
Expand Down
4 changes: 2 additions & 2 deletions stl/inc/memory
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ _NoThrowFwdIt uninitialized_copy_n(const _InIt _First, const _Diff _Count_raw, _
_Uninitialized_backout<decltype(_UDest)> _Backout{_UDest};

for (; _Count > 0; --_Count, (void) ++_UFirst) {
_Backout._Emplace_back(*_UFirst);
_Backout._Emplace_back_deref(_UFirst);
}

_UDest = _Backout._Release();
Expand Down Expand Up @@ -294,7 +294,7 @@ pair<_InIt, _NoThrowFwdIt> uninitialized_move_n(_InIt _First, const _Diff _Count
_Uninitialized_backout<decltype(_UDest)> _Backout{_UDest};

for (; _Count > 0; --_Count, (void) ++_UFirst) {
_Backout._Emplace_back(_STD move(*_UFirst));
_Backout._Emplace_back_deref_move(_UFirst);
}

_UDest = _Backout._Release();
Expand Down
49 changes: 35 additions & 14 deletions stl/inc/xmemory
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,12 @@ void _Return_temporary_buffer(_Ty* const _Pbuf) noexcept {
}
}

template <class _Ty, class _InIt>
void _Construct_in_place_by_deref(_Ty& _Val, const _InIt& _Iter)
noexcept(noexcept(::new (static_cast<void*>(_STD addressof(_Val))) _Ty(*_Iter))) {
::new (static_cast<void*>(_STD addressof(_Val))) _Ty(*_Iter);
}

template <class _NoThrowFwdIt>
struct _NODISCARD _Uninitialized_backout {
// struct to undo partially constructed ranges in _Uninitialized_xxx algorithms
Expand All @@ -1625,26 +1631,43 @@ struct _NODISCARD _Uninitialized_backout {
++_Last;
}

template <class _InIt>
void _Emplace_back_deref(const _InIt& _Iter) {
// construct a new element at *_Last from the result of dereferencing _Iter and increment.
_STD _Construct_in_place_by_deref(*_Last, _Iter);
++_Last;
}

template <class _InIt>
void _Emplace_back_deref_move(const _InIt& _Iter) {
// construct a new element at *_Last from the result of dereferencing _Iter and increment,
// with lvalue cast to xvalue if necessary for uninitialized_move(_n).
if constexpr (is_lvalue_reference_v<decltype(*_Iter)>) {
_STD _Construct_in_place(*_Last, _STD move(*_Iter));
} else {
_STD _Construct_in_place_by_deref(*_Last, _Iter);
}
++_Last;
}

constexpr _NoThrowFwdIt _Release() { // suppress any exception handling backout and return _Last
_First = _Last;
return _Last;
}
};

template <class _InIt, class _NoThrowFwdIt>
_CONSTEXPR20 _NoThrowFwdIt _Uninitialized_move_unchecked(_InIt _First, const _InIt _Last, _NoThrowFwdIt _Dest) {
_NoThrowFwdIt _Uninitialized_move_unchecked(_InIt _First, const _InIt _Last, _NoThrowFwdIt _Dest) {
// move [_First, _Last) to raw [_Dest, ...)
if constexpr (_Iter_move_cat<_InIt, _NoThrowFwdIt>::_Bitcopy_constructible) {
#if _HAS_CXX20
#if 0 // TRANSITION, _HAS_CXX26
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
if (!_STD is_constant_evaluated())
#endif // _HAS_CXX20
{
return _STD _Copy_memmove(_First, _Last, _Dest);
}
#endif // _HAS_CXX26
{ return _STD _Copy_memmove(_First, _Last, _Dest); }
}
_Uninitialized_backout<_NoThrowFwdIt> _Backout{_Dest};
for (; _First != _Last; ++_First) {
_Backout._Emplace_back(_STD move(*_First));
_Backout._Emplace_back_deref_move(_First);
frederick-vs-ja marked this conversation as resolved.
Show resolved Hide resolved
}

return _Backout._Release();
Expand Down Expand Up @@ -1905,20 +1928,18 @@ _CONSTEXPR20 _Alloc_ptr_t<_Alloc> _Uninitialized_copy_n(
}

template <class _InIt, class _NoThrowFwdIt>
_CONSTEXPR20 _NoThrowFwdIt _Uninitialized_copy_unchecked(_InIt _First, const _InIt _Last, _NoThrowFwdIt _Dest) {
_NoThrowFwdIt _Uninitialized_copy_unchecked(_InIt _First, const _InIt _Last, _NoThrowFwdIt _Dest) {
// copy [_First, _Last) to raw [_Dest, ...)
if constexpr (_Iter_copy_cat<_InIt, _NoThrowFwdIt>::_Bitcopy_constructible) {
#if _HAS_CXX20
#if 0 // TRANSITION, _HAS_CXX26
if (!_STD is_constant_evaluated())
#endif // _HAS_CXX20
{
return _STD _Copy_memmove(_First, _Last, _Dest);
}
#endif // _HAS_CXX26
{ return _STD _Copy_memmove(_First, _Last, _Dest); }
}

_Uninitialized_backout<_NoThrowFwdIt> _Backout{_Dest};
for (; _First != _Last; ++_First) {
_Backout._Emplace_back(*_First);
_Backout._Emplace_back_deref(_First);
}

return _Backout._Release();
Expand Down
125 changes: 125 additions & 0 deletions tests/std/tests/P0040R3_extending_memory_management_tools/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ template <typename T, size_t Count>
struct uninitialized_storage {
alignas(T) char storage[sizeof(T) * Count];

uninitialized_storage() {
fill(std::begin(storage), std::end(storage), fillChar);
}

T* begin() {
return &reinterpret_cast<T&>(storage);
}
Expand Down Expand Up @@ -196,6 +200,122 @@ void test_destroy_n() {
assert(g_alive == 0);
}

struct copy_elision_dest;

class pinned {
public:
explicit pinned(int n) : n_{n} {}

pinned(const pinned&) = delete;
pinned& operator=(const pinned&) = delete;

private:
friend copy_elision_dest;

int n_;
};

class pinned_ioterator {
private:
struct arrow_proxy {
pinned val_;

pinned* operator->() {
return &val_;
}
};

public:
using iterator_category = input_iterator_tag;
using difference_type = int;
using value_type = pinned;
using pointer = arrow_proxy;
using reference = pinned;

explicit pinned_ioterator(int n) : n_{n} {}

pinned operator*() const {
return pinned{n_};
}
pinned_ioterator& operator++() {
++n_;
return *this;
}
pinned_ioterator operator++(int) {
auto old = *this;
++*this;
return old;
}

arrow_proxy operator->() const {
return arrow_proxy{pinned{n_}};
}

friend bool operator==(pinned_ioterator i, pinned_ioterator j) {
return i.n_ == j.n_;
}
#if !_HAS_CXX20
friend bool operator!=(pinned_ioterator i, pinned_ioterator j) {
return !(i == j);
}
#endif // !_HAS_CXX20

private:
int n_;
};

struct copy_elision_dest {
explicit copy_elision_dest(pinned x) : n_{x.n_} {}

int n_;
};

// std::uninitialized_copy/_n are required to perform guaranteed copy elision since C++17.
void test_guaranteed_copy_elision_uninitialized_copy() {
constexpr int len = 42;

uninitialized_storage<copy_elision_dest, len> us;
uninitialized_copy(pinned_ioterator{0}, pinned_ioterator{len}, us.begin());
for (int i = 0; i != len; ++i) {
assert(us.begin()[i].n_ == i);
}
destroy(us.begin(), us.end());
}

void test_guaranteed_copy_elision_uninitialized_copy_n() {
constexpr int len = 42;

uninitialized_storage<copy_elision_dest, len> us;
uninitialized_copy_n(pinned_ioterator{0}, len, us.begin());
for (int i = 0; i != len; ++i) {
assert(us.begin()[i].n_ == i);
}
destroy(us.begin(), us.end());
}

// Also test LWG-3918 "std::uninitialized_move/_n and guaranteed copy elision".
void test_guaranteed_copy_elision_uninitialized_move() {
constexpr int len = 42;

uninitialized_storage<copy_elision_dest, len> us;
uninitialized_move(pinned_ioterator{0}, pinned_ioterator{len}, us.begin());
for (int i = 0; i != len; ++i) {
assert(us.begin()[i].n_ == i);
}
destroy(us.begin(), us.end());
}

void test_guaranteed_copy_elision_uninitialized_move_n() {
constexpr int len = 42;

uninitialized_storage<copy_elision_dest, len> us;
uninitialized_move_n(pinned_ioterator{0}, len, us.begin());
for (int i = 0; i != len; ++i) {
assert(us.begin()[i].n_ == i);
}
destroy(us.begin(), us.end());
}

int main() {
test_uninitialized_move();
test_uninitialized_move_n();
Expand All @@ -206,4 +326,9 @@ int main() {
test_destroy_at();
test_destroy();
test_destroy_n();

test_guaranteed_copy_elision_uninitialized_copy();
test_guaranteed_copy_elision_uninitialized_copy_n();
test_guaranteed_copy_elision_uninitialized_move();
test_guaranteed_copy_elision_uninitialized_move_n();
}
6 changes: 6 additions & 0 deletions tests/std/tests/P0784R7_library_machinery/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ constexpr bool test() {
assert(equal(begin(expected_copy), end(expected_copy), begin(output), end(output)));
}

#if 1 // TRANSITION, !_HAS_CXX26
if (!is_constant_evaluated())
#endif // !_HAS_CXX26
{ // _Uninitialized_copy_unchecked
int_wrapper_copy input[] = {1, 2, 3, 4};
int_wrapper_copy output[4];
Expand Down Expand Up @@ -133,6 +136,9 @@ constexpr bool test() {
}
}

#if 1 // TRANSITION, !_HAS_CXX26
if (!is_constant_evaluated())
#endif // !_HAS_CXX26
{ // _Uninitialized_move_unchecked
int_wrapper_move input[] = {1, 2, 3, 4};
int_wrapper_move output[4];
Expand Down