Skip to content

Commit 7f083b9

Browse files
Implement LWG-3436 std::construct_at should support arrays (#5920)
Co-authored-by: Stephan T. Lavavej <stl@microsoft.com>
1 parent 3f638e3 commit 7f083b9

File tree

4 files changed

+175
-4
lines changed

4 files changed

+175
-4
lines changed

stl/inc/memory

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ namespace ranges {
533533

534534
struct _Construct_at_fn {
535535
template <class _Ty, class... _Types>
536-
requires requires(_Ty* _Ptr, _Types&&... _Args) {
536+
requires (!is_unbounded_array_v<_Ty>) && requires(_Ty* _Ptr, _Types&&... _Args) {
537537
::new (static_cast<void*>(_Ptr)) _Ty(static_cast<_Types &&>(_Args)...); // per LWG-3888
538538
}
539539
_STATIC_CALL_OPERATOR constexpr _Ty* operator()(_Ty* _Location, _Types&&... _Args) _CONST_CALL_OPERATOR
@@ -542,7 +542,26 @@ namespace ranges {
542542
#ifdef __EDG__
543543
return _STD construct_at(_Location, _STD forward<_Types>(_Args)...);
544544
#else // ^^^ EDG / Other vvv
545-
_MSVC_CONSTEXPR return ::new (static_cast<void*>(_Location)) _Ty(_STD forward<_Types>(_Args)...);
545+
if constexpr (is_array_v<_Ty>) {
546+
static_assert(sizeof...(_Types) == 0, "The array is only allowed to be value-initialized by "
547+
"std::ranges::construct_at. (N5032 [specialized.construct]/2)");
548+
#if defined(__clang__) // TRANSITION, LLVM-117294
549+
::new (static_cast<void*>(_Location)) _Ty();
550+
return __builtin_launder(_Location); // per old resolution of LWG-3436
551+
#elif defined(_MSC_VER) // TRANSITION, DevCom-10798069
552+
if constexpr (is_trivially_destructible_v<_Ty>) {
553+
_MSVC_CONSTEXPR return ::new (_Secret_placement_new_tag{}, static_cast<void*>(_Location)) _Ty[1]();
554+
} else {
555+
// For non-trivially-destructible types, the workaround doesn't work
556+
// because additional space is required to record the number of class objects to destroy.
557+
return ::new (static_cast<void*>(_Location)) _Ty[1]();
558+
}
559+
#else // ^^^ workaround / no workaround vvv
560+
_MSVC_CONSTEXPR return ::new (static_cast<void*>(_Location)) _Ty[1]();
561+
#endif // ^^^ no workaround ^^^
562+
} else {
563+
_MSVC_CONSTEXPR return ::new (static_cast<void*>(_Location)) _Ty(_STD forward<_Types>(_Args)...);
564+
}
546565
#endif // ^^^ Other ^^^
547566
}
548567
};

stl/inc/xutility

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,25 @@ __declspec(noalias) size_t __stdcall __std_mismatch_8(const void* _First1, const
249249

250250
} // extern "C"
251251

252+
#if _HAS_CXX20 && !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-10798069
253+
_STD_BEGIN
254+
struct _Secret_placement_new_tag {
255+
explicit _Secret_placement_new_tag() = default;
256+
};
257+
_STD_END
258+
259+
template <_STD same_as<_STD _Secret_placement_new_tag> _Tag>
260+
_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(_Size)
261+
_Post_satisfies_(return == _Where) constexpr void* __CRTDECL operator new[](
262+
size_t _Size, _Tag, _Writable_bytes_(_Size) void* _Where) noexcept {
263+
(void) _Size;
264+
return _Where;
265+
}
266+
267+
template <_STD same_as<_STD _Secret_placement_new_tag> _Tag>
268+
constexpr void __CRTDECL operator delete[](void*, _Tag, void*) noexcept {}
269+
#endif // ^^^ workaround ^^^
270+
252271
_STD_BEGIN
253272

254273
template <class _Target, class _Source>
@@ -590,12 +609,31 @@ struct _Get_rebind_alias<_Ty, _Other, void_t<typename _Ty::template rebind<_Othe
590609

591610
#if _HAS_CXX20
592611
_EXPORT_STD template <class _Ty, class... _Types>
593-
requires requires(_Ty* _Location, _Types&&... _Args) {
612+
requires (!is_unbounded_array_v<_Ty>) && requires(_Ty* _Location, _Types&&... _Args) {
594613
::new (static_cast<void*>(_Location)) _Ty(_STD forward<_Types>(_Args)...); // per LWG-3888
595614
}
596615
constexpr _Ty* construct_at(_Ty* const _Location, _Types&&... _Args)
597616
noexcept(noexcept(::new (static_cast<void*>(_Location)) _Ty(_STD forward<_Types>(_Args)...))) /* strengthened */ {
598-
_MSVC_CONSTEXPR return ::new (static_cast<void*>(_Location)) _Ty(_STD forward<_Types>(_Args)...);
617+
if constexpr (is_array_v<_Ty>) {
618+
static_assert(sizeof...(_Types) == 0, "The array is only allowed to be value-initialized by std::construct_at. "
619+
"(N5032 [specialized.construct]/2)");
620+
#if defined(__clang__) || defined(__EDG__) // TRANSITION, LLVM-117294, DevCom-10798145
621+
::new (static_cast<void*>(_Location)) _Ty();
622+
return __builtin_launder(_Location); // per old resolution of LWG-3436
623+
#elif defined(_MSC_VER) // TRANSITION, DevCom-10798069
624+
if constexpr (is_trivially_destructible_v<_Ty>) {
625+
_MSVC_CONSTEXPR return ::new (_Secret_placement_new_tag{}, static_cast<void*>(_Location)) _Ty[1]();
626+
} else {
627+
// For non-trivially-destructible types, the workaround doesn't work
628+
// because additional space is required to record the number of class objects to destroy.
629+
return ::new (static_cast<void*>(_Location)) _Ty[1]();
630+
}
631+
#else // ^^^ workaround / no workaround vvv
632+
_MSVC_CONSTEXPR return ::new (static_cast<void*>(_Location)) _Ty[1]();
633+
#endif // ^^^ no workaround ^^^
634+
} else {
635+
_MSVC_CONSTEXPR return ::new (static_cast<void*>(_Location)) _Ty(_STD forward<_Types>(_Args)...);
636+
}
599637
}
600638
#endif // _HAS_CXX20
601639

tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ static_assert(!can_construct_at<const volatile int>);
6767
static_assert(!can_construct_at<const volatile int, int>);
6868
static_assert(!can_construct_at<const volatile int, int&>);
6969

70+
static_assert(can_construct_at<int[1]>);
71+
static_assert(can_construct_at<int[1][42]>);
72+
static_assert(!can_construct_at<int[]>);
73+
static_assert(!can_construct_at<int[][42]>);
74+
7075
struct X {};
7176

7277
static_assert(!can_construct_at<int, X>);
@@ -619,6 +624,72 @@ static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::dest
619624
static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::destroy_n(arr + 0, 1); }>);
620625
#endif // ^^^ no workaround ^^^
621626

627+
// Test LWG-3436 "std::construct_at should support arrays"
628+
template <class T, size_t N>
629+
constexpr void test_std_construct_at_array() {
630+
union U {
631+
constexpr U() {}
632+
constexpr ~U() {}
633+
634+
T a[N];
635+
};
636+
U u;
637+
construct_at(&u.a);
638+
for (const auto& elem : u.a) {
639+
assert(elem == T{});
640+
}
641+
destroy_at(&u.a);
642+
}
643+
644+
template <class T, size_t N>
645+
constexpr void test_ranges_construct_at_array() {
646+
union U {
647+
constexpr U() {}
648+
constexpr ~U() {}
649+
650+
T a[N];
651+
};
652+
U u;
653+
ranges::construct_at(&u.a);
654+
for (const auto& elem : u.a) {
655+
assert(elem == T{});
656+
}
657+
ranges::destroy_at(&u.a);
658+
}
659+
660+
constexpr bool test_construct_at_array() {
661+
test_std_construct_at_array<int, 1>();
662+
test_std_construct_at_array<int, 42>();
663+
test_ranges_construct_at_array<int, 1>();
664+
test_ranges_construct_at_array<int, 42>();
665+
666+
#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-10798069
667+
if (!is_constant_evaluated())
668+
#endif // ^^^ workaround ^^^
669+
{
670+
#if !_HAS_CXX23
671+
if (!is_constant_evaluated())
672+
#endif // !_HAS_CXX23
673+
{
674+
test_std_construct_at_array<unique_ptr<string>, 1>();
675+
test_std_construct_at_array<unique_ptr<string>, 42>();
676+
test_ranges_construct_at_array<unique_ptr<string>, 1>();
677+
test_ranges_construct_at_array<unique_ptr<string>, 42>();
678+
}
679+
test_std_construct_at_array<string, 1>();
680+
test_ranges_construct_at_array<string, 1>();
681+
#if defined(__EDG__) && _ITERATOR_DEBUG_LEVEL != 0 // TRANSITION, DevCom-11012299
682+
if (!is_constant_evaluated())
683+
#endif // ^^^ workaround ^^^
684+
{
685+
test_std_construct_at_array<string, 42>();
686+
test_ranges_construct_at_array<string, 42>();
687+
}
688+
}
689+
690+
return true;
691+
}
692+
622693
int main() {
623694
test_runtime(1234);
624695
test_runtime(string("hello world"));
@@ -637,4 +708,7 @@ int main() {
637708
test_array(1234);
638709
test_array(string("hello world"));
639710
test_array(string("hello to some really long world that certainly doesn't fit in SSO"));
711+
712+
test_construct_at_array();
713+
static_assert(test_construct_at_array());
640714
}

tests/std/tests/P1004R2_constexpr_vector/test.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,11 +657,51 @@ constexpr bool test_growth() {
657657
return true;
658658
}
659659

660+
#pragma warning(push)
661+
#pragma warning(disable : 4582) // '%s': constructor is not implicitly called
662+
#pragma warning(disable : 4583) // '%s': destructor is not implicitly called
663+
template <class T, size_t N>
664+
constexpr void test_vector_of_array_impl() {
665+
vector<T[N]> v(42);
666+
for (const auto& a : v) {
667+
for (const auto& elem : a) {
668+
assert(elem == T{});
669+
}
670+
}
671+
}
672+
673+
constexpr bool test_vector_of_array() {
674+
test_vector_of_array_impl<int, 1>();
675+
test_vector_of_array_impl<int, 42>();
676+
677+
#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-10798069
678+
if (!is_constant_evaluated())
679+
#endif // ^^^ workaround ^^^
680+
{
681+
#if !_HAS_CXX23
682+
if (!is_constant_evaluated())
683+
#endif // !_HAS_CXX23
684+
{
685+
test_vector_of_array_impl<unique_ptr<vector<char>>, 1>();
686+
test_vector_of_array_impl<unique_ptr<vector<char>>, 42>();
687+
}
688+
test_vector_of_array_impl<vector<long>, 1>();
689+
test_vector_of_array_impl<vector<long>, 42>();
690+
}
691+
692+
return true;
693+
}
694+
#pragma warning(pop)
695+
660696
int main() {
661697
test_interface();
662698
test_iterators();
663699
test_growth();
700+
test_vector_of_array();
664701
static_assert(test_interface());
665702
static_assert(test_iterators());
666703
static_assert(test_growth());
704+
#ifndef __EDG__ // TRANSITION, DevCom-11008487
705+
static_assert(test_vector_of_array());
706+
#endif // ^^^ no workaround ^^^
667707
}

0 commit comments

Comments
 (0)