From c7f9ee3c2c058743a33254a28244d25a9c53ea41 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Mon, 1 Dec 2025 07:36:04 +0800 Subject: [PATCH 1/5] Implement LWG-3436 `std::construct_at` should support arrays --- stl/inc/memory | 23 +++++- stl/inc/xutility | 42 ++++++++++- .../test.cpp | 75 +++++++++++++++++++ .../tests/P1004R2_constexpr_vector/test.cpp | 44 +++++++++++ 4 files changed, 180 insertions(+), 4 deletions(-) diff --git a/stl/inc/memory b/stl/inc/memory index 7abee62f027..2336b9707c2 100644 --- a/stl/inc/memory +++ b/stl/inc/memory @@ -538,7 +538,7 @@ namespace ranges { class _Construct_at_fn { public: template - requires requires(_Ty* _Ptr, _Types&&... _Args) { + requires (!is_unbounded_array_v<_Ty>) && requires(_Ty* _Ptr, _Types&&... _Args) { ::new (static_cast(_Ptr)) _Ty(static_cast<_Types &&>(_Args)...); // per LWG-3888 } _STATIC_CALL_OPERATOR constexpr _Ty* operator()(_Ty* _Location, _Types&&... _Args) _CONST_CALL_OPERATOR @@ -547,7 +547,26 @@ namespace ranges { #ifdef __EDG__ return _STD construct_at(_Location, _STD forward<_Types>(_Args)...); #else // ^^^ EDG / Other vvv - _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...); + if constexpr (is_array_v<_Ty>) { + static_assert(sizeof...(_Types) == 0, "The array is only allowed to be value-initialized by " + "std::ranges::construct_at. (N5014 [specialized.construct]/2)"); +#if defined(__clang__) // TRANSITION, LLVM-117294 + ::new (static_cast(_Location)) _Ty(); + return __builtin_launder(_Location); // per old resolution of LWG-3436 +#elif defined(_MSC_VER) // TRANSITION, DevCom-10798069 + if constexpr (is_trivially_destructible_v<_Ty>) { + _MSVC_CONSTEXPR return ::new (_Secret_placement_new_tag{}, static_cast(_Location)) _Ty[1](); + } else { + // For non-trivially-destructible types, the workaround doesn't work. + // Because additional space is required to record the number of class objects to destroy. + return ::new (static_cast(_Location)) _Ty[1](); + } +#else // ^^^ workaround / no workaround vvv + _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty[1](); +#endif // ^^^ no workaround ^^^ + } else { + _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...); + } #endif // ^^^ Other ^^^ } }; diff --git a/stl/inc/xutility b/stl/inc/xutility index e2b4083cf83..5eab2ce38da 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -252,6 +252,25 @@ __declspec(noalias) size_t __stdcall __std_mismatch_8(const void* _First1, const } // extern "C" +#if _HAS_CXX20 && !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-10798069 +_STD_BEGIN +struct _Secret_placement_new_tag { + explicit _Secret_placement_new_tag() = default; +}; +_STD_END + +template <_STD same_as<_STD _Secret_placement_new_tag> _Tag> +_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(_Size) + _Post_satisfies_(return == _Where) constexpr void* __CRTDECL operator new[]( + size_t _Size, _Tag, _Writable_bytes_(_Size) void* _Where) noexcept { + (void) _Size; + return _Where; +} + +template <_STD same_as<_STD _Secret_placement_new_tag> _Tag> +constexpr void __CRTDECL operator delete[](void*, _Tag, void*) noexcept {} +#endif // _HAS_CXX20 && !defined(__clang__) && !defined(__EDG__) + _STD_BEGIN template @@ -585,12 +604,31 @@ struct _Get_rebind_alias<_Ty, _Other, void_t - requires requires(_Ty* _Location, _Types&&... _Args) { + requires (!is_unbounded_array_v<_Ty>) && requires(_Ty* _Location, _Types&&... _Args) { ::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...); // per LWG-3888 } constexpr _Ty* construct_at(_Ty* const _Location, _Types&&... _Args) noexcept(noexcept(::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...))) /* strengthened */ { - _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...); + if constexpr (is_array_v<_Ty>) { + static_assert(sizeof...(_Types) == 0, "The array is only allowed to be value-initialized by std::construct_at. " + "(N5014 [specialized.construct]/2)"); +#if defined(__clang__) || defined(__EDG__) // TRANSITION, LLVM-117294, DevCom-10798145 + ::new (static_cast(_Location)) _Ty(); + return __builtin_launder(_Location); // per old resolution of LWG-3436 +#elif defined(_MSC_VER) // TRANSITION, DevCom-10798069 + if constexpr (is_trivially_destructible_v<_Ty>) { + _MSVC_CONSTEXPR return ::new (_Secret_placement_new_tag{}, static_cast(_Location)) _Ty[1](); + } else { + // For non-trivially-destructible types, the workaround doesn't work. + // Because additional space is required to record the number of class objects to destroy. + return ::new (static_cast(_Location)) _Ty[1](); + } +#else // ^^^ workaround / no workaround vvv + _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty[1](); +#endif // ^^^ no workaround ^^^ + } else { + _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...); + } } #endif // _HAS_CXX20 diff --git a/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp b/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp index 8b5299caa1d..701d9b579a3 100644 --- a/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp +++ b/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp @@ -67,6 +67,11 @@ static_assert(!can_construct_at); static_assert(!can_construct_at); static_assert(!can_construct_at); +static_assert(can_construct_at); +static_assert(can_construct_at); +static_assert(!can_construct_at); +static_assert(!can_construct_at); + struct X {}; static_assert(!can_construct_at); @@ -619,6 +624,71 @@ static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::dest static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::destroy_n(arr + 0, 1); }>); #endif // ^^^ no workaround ^^^ +// Test LWG-3436 "std::construct_at should support arrays" +template +constexpr void test_construct_at_array() { + { + union U { + constexpr U() {} + constexpr ~U() {} + + T a[N]; + }; + U u; + construct_at(&u.a); + for (const auto& elem : u.a) { + assert(elem == T{}); + } + destroy_at(&u.a); + } +} + +template +constexpr void test_ranges_construct_at_array() { + { + union U { + constexpr U() {} + constexpr ~U() {} + + T a[N]; + }; + U u; + ranges::construct_at(&u.a); + for (const auto& elem : u.a) { + assert(elem == T{}); + } + ranges::destroy_at(&u.a); + } +} + +constexpr bool test_construct_at_array() { + test_construct_at_array(); + test_construct_at_array(); + test_ranges_construct_at_array(); + test_ranges_construct_at_array(); + +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-10798069 + if (!is_constant_evaluated()) +#endif // !defined(__clang__) && !defined(__EDG__) + { +#if !_HAS_CXX23 + if (!is_constant_evaluated()) +#endif // !_HAS_CXX23 + { + test_construct_at_array, 1>(); + test_construct_at_array, 42>(); + test_ranges_construct_at_array, 1>(); + test_ranges_construct_at_array, 42>(); + } + test_construct_at_array(); + test_construct_at_array(); + test_ranges_construct_at_array(); + test_ranges_construct_at_array(); + } + + return true; +} + int main() { test_runtime(1234); test_runtime(string("hello world")); @@ -637,4 +707,9 @@ int main() { test_array(1234); test_array(string("hello world")); test_array(string("hello to some really long world that certainly doesn't fit in SSO")); + + test_construct_at_array(); +#if !_HAS_CXX23 || !defined(__EDG__) // TRANSITION, EDG crashes, to be reported + static_assert(test_construct_at_array()); +#endif // !_HAS_CXX23 || !defined(__EDG__) } diff --git a/tests/std/tests/P1004R2_constexpr_vector/test.cpp b/tests/std/tests/P1004R2_constexpr_vector/test.cpp index a50b5383c62..1403f58539c 100644 --- a/tests/std/tests/P1004R2_constexpr_vector/test.cpp +++ b/tests/std/tests/P1004R2_constexpr_vector/test.cpp @@ -657,11 +657,55 @@ constexpr bool test_growth() { return true; } +#if !defined(__clang__) && !defined(__EDG__) +#pragma warning(push) +#pragma warning(disable : 4582) // '%s': constructor is not implicitly called +#pragma warning(disable : 4583) // '%s': destructor is not implicitly called +#endif // !defined(__clang__) && !defined(__EDG__) +template +constexpr void test_vector_of_array() { + vector v(42); + for (const auto& a : v) { + for (const auto& elem : a) { + assert(elem == T{}); + } + } +} + +constexpr bool test_vector_of_array() { + test_vector_of_array(); + test_vector_of_array(); + +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-10798069 + if (!is_constant_evaluated()) +#endif // !defined(__clang__) && !defined(__EDG__) + { +#if !_HAS_CXX23 + if (!is_constant_evaluated()) +#endif // !_HAS_CXX23 + { + test_vector_of_array>, 1>(); + test_vector_of_array>, 42>(); + } + test_vector_of_array, 1>(); + test_vector_of_array, 42>(); + } + + return true; +} +#if !defined(__clang__) && !defined(__EDG__) +#pragma warning(pop) +#endif // !defined(__clang__) && !defined(__EDG__) + int main() { test_interface(); test_iterators(); test_growth(); + test_vector_of_array(); static_assert(test_interface()); static_assert(test_iterators()); static_assert(test_growth()); +#ifndef __EDG__ // TRANSITION, DevCom-11008487 + static_assert(test_vector_of_array()); +#endif // !defined(__EDG__) } From 9ed6cd9765eeaace8422e4f20d4258839638ac8d Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Sun, 7 Dec 2025 21:05:44 +0800 Subject: [PATCH 2/5] Reduce braces in the test --- .../test.cpp | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp b/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp index 701d9b579a3..ae0bcaaf4a8 100644 --- a/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp +++ b/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp @@ -627,38 +627,34 @@ static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::dest // Test LWG-3436 "std::construct_at should support arrays" template constexpr void test_construct_at_array() { - { - union U { - constexpr U() {} - constexpr ~U() {} - - T a[N]; - }; - U u; - construct_at(&u.a); - for (const auto& elem : u.a) { - assert(elem == T{}); - } - destroy_at(&u.a); + union U { + constexpr U() {} + constexpr ~U() {} + + T a[N]; + }; + U u; + construct_at(&u.a); + for (const auto& elem : u.a) { + assert(elem == T{}); } + destroy_at(&u.a); } template constexpr void test_ranges_construct_at_array() { - { - union U { - constexpr U() {} - constexpr ~U() {} - - T a[N]; - }; - U u; - ranges::construct_at(&u.a); - for (const auto& elem : u.a) { - assert(elem == T{}); - } - ranges::destroy_at(&u.a); + union U { + constexpr U() {} + constexpr ~U() {} + + T a[N]; + }; + U u; + ranges::construct_at(&u.a); + for (const auto& elem : u.a) { + assert(elem == T{}); } + ranges::destroy_at(&u.a); } constexpr bool test_construct_at_array() { From f5a710423ff33ba30d22817a511509bd113d4d4a Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Sun, 7 Dec 2025 21:05:55 +0800 Subject: [PATCH 3/5] Fix skipping for DevCom-11012299 --- .../test.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp b/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp index ae0bcaaf4a8..4649769b654 100644 --- a/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp +++ b/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp @@ -677,9 +677,19 @@ constexpr bool test_construct_at_array() { test_ranges_construct_at_array, 42>(); } test_construct_at_array(); - test_construct_at_array(); +#if defined(__EDG__) && _ITERATOR_DEBUG_LEVEL != 0 // TRANSITION, DevCom-11012299 + if (!is_constant_evaluated()) +#endif // ^^^ workaround ^^^ + { + test_construct_at_array(); + } test_ranges_construct_at_array(); - test_ranges_construct_at_array(); +#if defined(__EDG__) && _ITERATOR_DEBUG_LEVEL != 0 // TRANSITION, DevCom-11012299 + if (!is_constant_evaluated()) +#endif // ^^^ workaround ^^^ + { + test_ranges_construct_at_array(); + } } return true; @@ -705,7 +715,5 @@ int main() { test_array(string("hello to some really long world that certainly doesn't fit in SSO")); test_construct_at_array(); -#if !_HAS_CXX23 || !defined(__EDG__) // TRANSITION, EDG crashes, to be reported static_assert(test_construct_at_array()); -#endif // !_HAS_CXX23 || !defined(__EDG__) } From 9d68488cbf9a680cca5f1b9d44a1cbe15ce898b3 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Sun, 7 Dec 2025 21:10:03 +0800 Subject: [PATCH 4/5] Fix "(no) workaround" comments; remove `#pragma` guards --- stl/inc/xutility | 2 +- .../test.cpp | 2 +- tests/std/tests/P1004R2_constexpr_vector/test.cpp | 8 ++------ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/stl/inc/xutility b/stl/inc/xutility index 5eab2ce38da..d8ec2763c64 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -269,7 +269,7 @@ _NODISCARD _Ret_notnull_ _Post_writable_byte_size_(_Size) template <_STD same_as<_STD _Secret_placement_new_tag> _Tag> constexpr void __CRTDECL operator delete[](void*, _Tag, void*) noexcept {} -#endif // _HAS_CXX20 && !defined(__clang__) && !defined(__EDG__) +#endif // ^^^ workaround ^^^ _STD_BEGIN diff --git a/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp b/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp index 4649769b654..5bd21e741ec 100644 --- a/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp +++ b/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp @@ -665,7 +665,7 @@ constexpr bool test_construct_at_array() { #if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-10798069 if (!is_constant_evaluated()) -#endif // !defined(__clang__) && !defined(__EDG__) +#endif // ^^^ workaround ^^^ { #if !_HAS_CXX23 if (!is_constant_evaluated()) diff --git a/tests/std/tests/P1004R2_constexpr_vector/test.cpp b/tests/std/tests/P1004R2_constexpr_vector/test.cpp index 1403f58539c..d33fc45aa2d 100644 --- a/tests/std/tests/P1004R2_constexpr_vector/test.cpp +++ b/tests/std/tests/P1004R2_constexpr_vector/test.cpp @@ -657,11 +657,9 @@ constexpr bool test_growth() { return true; } -#if !defined(__clang__) && !defined(__EDG__) #pragma warning(push) #pragma warning(disable : 4582) // '%s': constructor is not implicitly called #pragma warning(disable : 4583) // '%s': destructor is not implicitly called -#endif // !defined(__clang__) && !defined(__EDG__) template constexpr void test_vector_of_array() { vector v(42); @@ -678,7 +676,7 @@ constexpr bool test_vector_of_array() { #if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-10798069 if (!is_constant_evaluated()) -#endif // !defined(__clang__) && !defined(__EDG__) +#endif // ^^^ workaround ^^^ { #if !_HAS_CXX23 if (!is_constant_evaluated()) @@ -693,9 +691,7 @@ constexpr bool test_vector_of_array() { return true; } -#if !defined(__clang__) && !defined(__EDG__) #pragma warning(pop) -#endif // !defined(__clang__) && !defined(__EDG__) int main() { test_interface(); @@ -707,5 +703,5 @@ int main() { static_assert(test_growth()); #ifndef __EDG__ // TRANSITION, DevCom-11008487 static_assert(test_vector_of_array()); -#endif // !defined(__EDG__) +#endif // ^^^ no workaround ^^^ } From 377bef18a019e571646dbaf548943cf8727df0e3 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Tue, 16 Dec 2025 09:03:07 +0800 Subject: [PATCH 5/5] Update to reference to WG21-N5032 --- stl/inc/memory | 2 +- stl/inc/xutility | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/memory b/stl/inc/memory index 0ce51ea1c62..d6d553ddd55 100644 --- a/stl/inc/memory +++ b/stl/inc/memory @@ -544,7 +544,7 @@ namespace ranges { #else // ^^^ EDG / Other vvv if constexpr (is_array_v<_Ty>) { static_assert(sizeof...(_Types) == 0, "The array is only allowed to be value-initialized by " - "std::ranges::construct_at. (N5014 [specialized.construct]/2)"); + "std::ranges::construct_at. (N5032 [specialized.construct]/2)"); #if defined(__clang__) // TRANSITION, LLVM-117294 ::new (static_cast(_Location)) _Ty(); return __builtin_launder(_Location); // per old resolution of LWG-3436 diff --git a/stl/inc/xutility b/stl/inc/xutility index 6b89cd42a82..7f92ee5c4b4 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -611,7 +611,7 @@ constexpr _Ty* construct_at(_Ty* const _Location, _Types&&... _Args) noexcept(noexcept(::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...))) /* strengthened */ { if constexpr (is_array_v<_Ty>) { static_assert(sizeof...(_Types) == 0, "The array is only allowed to be value-initialized by std::construct_at. " - "(N5014 [specialized.construct]/2)"); + "(N5032 [specialized.construct]/2)"); #if defined(__clang__) || defined(__EDG__) // TRANSITION, LLVM-117294, DevCom-10798145 ::new (static_cast(_Location)) _Ty(); return __builtin_launder(_Location); // per old resolution of LWG-3436