Skip to content

Commit 649a4f2

Browse files
Implement LWG-3918 std::uninitialized_move/_n and guaranteed copy elision (#5135)
Co-authored-by: Stephan T. Lavavej <[email protected]>
1 parent abefd5e commit 649a4f2

File tree

5 files changed

+168
-21
lines changed

5 files changed

+168
-21
lines changed

stl/inc/execution

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,6 @@ template <>
137137
struct is_execution_policy<execution::unsequenced_policy> : true_type {};
138138
#endif // _HAS_CXX20
139139

140-
template <class _Ty, class _FwdIt>
141-
void _Construct_in_place_by_deref(_Ty& _Val, const _FwdIt& _Iter) {
142-
::new (static_cast<void*>(_STD addressof(_Val))) _Ty(*_Iter);
143-
}
144-
145140
template <class _Ty, class _UnaryOp, class _FwdIt>
146141
void _Construct_in_place_by_transform_deref(_Ty& _Val, _UnaryOp _Transform_op, const _FwdIt& _Iter) {
147142
::new (static_cast<void*>(_STD addressof(_Val))) _Ty(_Transform_op(*_Iter));

stl/inc/memory

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ _NoThrowFwdIt uninitialized_copy_n(const _InIt _First, const _Diff _Count_raw, _
141141
_Uninitialized_backout<decltype(_UDest)> _Backout{_UDest};
142142

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

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

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

300300
_UDest = _Backout._Release();

stl/inc/xmemory

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,12 @@ void _Return_temporary_buffer(_Ty* const _Pbuf) noexcept {
16011601
}
16021602
}
16031603

1604+
template <class _Ty, class _InIt>
1605+
void _Construct_in_place_by_deref(_Ty& _Val, const _InIt& _Iter)
1606+
noexcept(noexcept(::new (static_cast<void*>(_STD addressof(_Val))) _Ty(*_Iter))) {
1607+
::new (static_cast<void*>(_STD addressof(_Val))) _Ty(*_Iter);
1608+
}
1609+
16041610
template <class _NoThrowFwdIt>
16051611
struct _NODISCARD _Uninitialized_backout {
16061612
// struct to undo partially constructed ranges in _Uninitialized_xxx algorithms
@@ -1625,26 +1631,43 @@ struct _NODISCARD _Uninitialized_backout {
16251631
++_Last;
16261632
}
16271633

1634+
template <class _InIt>
1635+
void _Emplace_back_deref(const _InIt& _Iter) {
1636+
// construct a new element at *_Last from the result of dereferencing _Iter and increment.
1637+
_STD _Construct_in_place_by_deref(*_Last, _Iter);
1638+
++_Last;
1639+
}
1640+
1641+
template <class _InIt>
1642+
void _Emplace_back_deref_move(const _InIt& _Iter) {
1643+
// construct a new element at *_Last from the result of dereferencing _Iter and increment,
1644+
// with lvalue cast to xvalue if necessary for uninitialized_move(_n).
1645+
if constexpr (is_lvalue_reference_v<decltype(*_Iter)>) {
1646+
_STD _Construct_in_place(*_Last, _STD move(*_Iter));
1647+
} else {
1648+
_STD _Construct_in_place_by_deref(*_Last, _Iter);
1649+
}
1650+
++_Last;
1651+
}
1652+
16281653
constexpr _NoThrowFwdIt _Release() { // suppress any exception handling backout and return _Last
16291654
_First = _Last;
16301655
return _Last;
16311656
}
16321657
};
16331658

16341659
template <class _InIt, class _NoThrowFwdIt>
1635-
_CONSTEXPR20 _NoThrowFwdIt _Uninitialized_move_unchecked(_InIt _First, const _InIt _Last, _NoThrowFwdIt _Dest) {
1660+
_NoThrowFwdIt _Uninitialized_move_unchecked(_InIt _First, const _InIt _Last, _NoThrowFwdIt _Dest) {
16361661
// move [_First, _Last) to raw [_Dest, ...)
16371662
if constexpr (_Iter_move_cat<_InIt, _NoThrowFwdIt>::_Bitcopy_constructible) {
1638-
#if _HAS_CXX20
1663+
#if 0 // TRANSITION, _HAS_CXX26
16391664
if (!_STD is_constant_evaluated())
1640-
#endif // _HAS_CXX20
1641-
{
1642-
return _STD _Copy_memmove(_First, _Last, _Dest);
1643-
}
1665+
#endif // _HAS_CXX26
1666+
{ return _STD _Copy_memmove(_First, _Last, _Dest); }
16441667
}
16451668
_Uninitialized_backout<_NoThrowFwdIt> _Backout{_Dest};
16461669
for (; _First != _Last; ++_First) {
1647-
_Backout._Emplace_back(_STD move(*_First));
1670+
_Backout._Emplace_back_deref_move(_First);
16481671
}
16491672

16501673
return _Backout._Release();
@@ -1905,20 +1928,18 @@ _CONSTEXPR20 _Alloc_ptr_t<_Alloc> _Uninitialized_copy_n(
19051928
}
19061929

19071930
template <class _InIt, class _NoThrowFwdIt>
1908-
_CONSTEXPR20 _NoThrowFwdIt _Uninitialized_copy_unchecked(_InIt _First, const _InIt _Last, _NoThrowFwdIt _Dest) {
1931+
_NoThrowFwdIt _Uninitialized_copy_unchecked(_InIt _First, const _InIt _Last, _NoThrowFwdIt _Dest) {
19091932
// copy [_First, _Last) to raw [_Dest, ...)
19101933
if constexpr (_Iter_copy_cat<_InIt, _NoThrowFwdIt>::_Bitcopy_constructible) {
1911-
#if _HAS_CXX20
1934+
#if 0 // TRANSITION, _HAS_CXX26
19121935
if (!_STD is_constant_evaluated())
1913-
#endif // _HAS_CXX20
1914-
{
1915-
return _STD _Copy_memmove(_First, _Last, _Dest);
1916-
}
1936+
#endif // _HAS_CXX26
1937+
{ return _STD _Copy_memmove(_First, _Last, _Dest); }
19171938
}
19181939

19191940
_Uninitialized_backout<_NoThrowFwdIt> _Backout{_Dest};
19201941
for (; _First != _Last; ++_First) {
1921-
_Backout._Emplace_back(*_First);
1942+
_Backout._Emplace_back_deref(_First);
19221943
}
19231944

19241945
return _Backout._Release();

tests/std/tests/P0040R3_extending_memory_management_tools/test.cpp

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ template <typename T, size_t Count>
5858
struct uninitialized_storage {
5959
alignas(T) char storage[sizeof(T) * Count];
6060

61+
uninitialized_storage() {
62+
fill(std::begin(storage), std::end(storage), fillChar);
63+
}
64+
6165
T* begin() {
6266
return &reinterpret_cast<T&>(storage);
6367
}
@@ -196,6 +200,122 @@ void test_destroy_n() {
196200
assert(g_alive == 0);
197201
}
198202

203+
struct copy_elision_dest;
204+
205+
class pinned {
206+
public:
207+
explicit pinned(int n) : n_{n} {}
208+
209+
pinned(const pinned&) = delete;
210+
pinned& operator=(const pinned&) = delete;
211+
212+
private:
213+
friend copy_elision_dest;
214+
215+
int n_;
216+
};
217+
218+
class pinned_ioterator {
219+
private:
220+
struct arrow_proxy {
221+
pinned val_;
222+
223+
pinned* operator->() {
224+
return &val_;
225+
}
226+
};
227+
228+
public:
229+
using iterator_category = input_iterator_tag;
230+
using difference_type = int;
231+
using value_type = pinned;
232+
using pointer = arrow_proxy;
233+
using reference = pinned;
234+
235+
explicit pinned_ioterator(int n) : n_{n} {}
236+
237+
pinned operator*() const {
238+
return pinned{n_};
239+
}
240+
pinned_ioterator& operator++() {
241+
++n_;
242+
return *this;
243+
}
244+
pinned_ioterator operator++(int) {
245+
auto old = *this;
246+
++*this;
247+
return old;
248+
}
249+
250+
arrow_proxy operator->() const {
251+
return arrow_proxy{pinned{n_}};
252+
}
253+
254+
friend bool operator==(pinned_ioterator i, pinned_ioterator j) {
255+
return i.n_ == j.n_;
256+
}
257+
#if !_HAS_CXX20
258+
friend bool operator!=(pinned_ioterator i, pinned_ioterator j) {
259+
return !(i == j);
260+
}
261+
#endif // !_HAS_CXX20
262+
263+
private:
264+
int n_;
265+
};
266+
267+
struct copy_elision_dest {
268+
explicit copy_elision_dest(pinned x) : n_{x.n_} {}
269+
270+
int n_;
271+
};
272+
273+
// std::uninitialized_copy/_n are required to perform guaranteed copy elision since C++17.
274+
void test_guaranteed_copy_elision_uninitialized_copy() {
275+
constexpr int len = 42;
276+
277+
uninitialized_storage<copy_elision_dest, len> us;
278+
uninitialized_copy(pinned_ioterator{0}, pinned_ioterator{len}, us.begin());
279+
for (int i = 0; i != len; ++i) {
280+
assert(us.begin()[i].n_ == i);
281+
}
282+
destroy(us.begin(), us.end());
283+
}
284+
285+
void test_guaranteed_copy_elision_uninitialized_copy_n() {
286+
constexpr int len = 42;
287+
288+
uninitialized_storage<copy_elision_dest, len> us;
289+
uninitialized_copy_n(pinned_ioterator{0}, len, us.begin());
290+
for (int i = 0; i != len; ++i) {
291+
assert(us.begin()[i].n_ == i);
292+
}
293+
destroy(us.begin(), us.end());
294+
}
295+
296+
// Also test LWG-3918 "std::uninitialized_move/_n and guaranteed copy elision".
297+
void test_guaranteed_copy_elision_uninitialized_move() {
298+
constexpr int len = 42;
299+
300+
uninitialized_storage<copy_elision_dest, len> us;
301+
uninitialized_move(pinned_ioterator{0}, pinned_ioterator{len}, us.begin());
302+
for (int i = 0; i != len; ++i) {
303+
assert(us.begin()[i].n_ == i);
304+
}
305+
destroy(us.begin(), us.end());
306+
}
307+
308+
void test_guaranteed_copy_elision_uninitialized_move_n() {
309+
constexpr int len = 42;
310+
311+
uninitialized_storage<copy_elision_dest, len> us;
312+
uninitialized_move_n(pinned_ioterator{0}, len, us.begin());
313+
for (int i = 0; i != len; ++i) {
314+
assert(us.begin()[i].n_ == i);
315+
}
316+
destroy(us.begin(), us.end());
317+
}
318+
199319
int main() {
200320
test_uninitialized_move();
201321
test_uninitialized_move_n();
@@ -206,4 +326,9 @@ int main() {
206326
test_destroy_at();
207327
test_destroy();
208328
test_destroy_n();
329+
330+
test_guaranteed_copy_elision_uninitialized_copy();
331+
test_guaranteed_copy_elision_uninitialized_copy_n();
332+
test_guaranteed_copy_elision_uninitialized_move();
333+
test_guaranteed_copy_elision_uninitialized_move_n();
209334
}

tests/std/tests/P0784R7_library_machinery/test.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ constexpr bool test() {
8484
assert(equal(begin(expected_copy), end(expected_copy), begin(output), end(output)));
8585
}
8686

87+
#if 1 // TRANSITION, !_HAS_CXX26
88+
if (!is_constant_evaluated())
89+
#endif // !_HAS_CXX26
8790
{ // _Uninitialized_copy_unchecked
8891
int_wrapper_copy input[] = {1, 2, 3, 4};
8992
int_wrapper_copy output[4];
@@ -133,6 +136,9 @@ constexpr bool test() {
133136
}
134137
}
135138

139+
#if 1 // TRANSITION, !_HAS_CXX26
140+
if (!is_constant_evaluated())
141+
#endif // !_HAS_CXX26
136142
{ // _Uninitialized_move_unchecked
137143
int_wrapper_move input[] = {1, 2, 3, 4};
138144
int_wrapper_move output[4];

0 commit comments

Comments
 (0)