-
Notifications
You must be signed in to change notification settings - Fork 15.1k
[libc++] Implement LWG3436: support for arrays in std::construct_at #132283
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
| #include <__new/placement_new_delete.h> | ||
| #include <__type_traits/enable_if.h> | ||
| #include <__type_traits/is_array.h> | ||
| #include <__type_traits/is_unbounded_array.h> | ||
| #include <__utility/declval.h> | ||
| #include <__utility/forward.h> | ||
| #include <__utility/move.h> | ||
|
|
@@ -34,15 +35,26 @@ _LIBCPP_BEGIN_NAMESPACE_STD | |
|
|
||
| #if _LIBCPP_STD_VER >= 20 | ||
|
|
||
| template <class _Tp, class... _Args, class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...))> | ||
| template <class _Tp, | ||
| class... _Args, | ||
| class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...)), | ||
| __enable_if_t<!is_unbounded_array_v<_Tp>, int> = 0> | ||
| _LIBCPP_HIDE_FROM_ABI constexpr _Tp* construct_at(_Tp* __location, _Args&&... __args) { | ||
| _LIBCPP_ASSERT_NON_NULL(__location != nullptr, "null pointer given to construct_at"); | ||
| return ::new (static_cast<void*>(__location)) _Tp(std::forward<_Args>(__args)...); | ||
| if constexpr (is_array_v<_Tp>) { | ||
| static_assert(sizeof...(_Args) == 0, "construction arguments cannot be passed to construct_at with an array type"); | ||
| return ::new (static_cast<void*>(__location)) _Tp[1](); | ||
| } else { | ||
| return ::new (static_cast<void*>(__location)) _Tp(std::forward<_Args>(__args)...); | ||
| } | ||
| } | ||
|
|
||
| #endif | ||
|
|
||
| template <class _Tp, class... _Args, class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...))> | ||
| template <class _Tp, | ||
| class... _Args, | ||
| class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...)), | ||
| __enable_if_t<!is_unbounded_array_v<_Tp>, int> = 0> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp* __construct_at(_Tp* __location, _Args&&... __args) { | ||
| #if _LIBCPP_STD_VER >= 20 | ||
| return std::construct_at(__location, std::forward<_Args>(__args)...); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,23 @@ | ||||||
| //===----------------------------------------------------------------------===// | ||||||
| // | ||||||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||||||
| // See https://llvm.org/LICENSE.txt for license information. | ||||||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||||||
| // | ||||||
| //===----------------------------------------------------------------------===// | ||||||
|
|
||||||
| // UNSUPPORTED: c++03, c++11, c++14, c++17 | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that we can use the new style (ditto below):
Suggested change
|
||||||
| // REQUIRES: stdlib=libc++ | ||||||
|
|
||||||
| // <memory> | ||||||
|
|
||||||
| // Test that std::construct_at provides a meaningful diagnostic when used with an | ||||||
| // array type and construction arguments are provided. See LWG3436. | ||||||
|
|
||||||
| #include <memory> | ||||||
|
|
||||||
| using Array = int[3]; | ||||||
| void test(Array* a) { | ||||||
| std::construct_at(a, 1, 2, 3); | ||||||
| // expected-error-re@*:* {{static assertion failed {{.*}}construction arguments cannot be passed to construct_at with an array type}} | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,11 +14,13 @@ | |
| // constexpr T* construct_at(T* location, Args&& ...args); | ||
|
|
||
| #include <cassert> | ||
| #include <concepts> | ||
| #include <cstddef> | ||
| #include <memory> | ||
| #include <utility> | ||
|
|
||
| #include "test_iterators.h" | ||
| #include "test_macros.h" | ||
|
|
||
| struct Foo { | ||
| constexpr Foo() {} | ||
|
|
@@ -39,25 +41,31 @@ struct Counted { | |
| constexpr ~Counted() { --count_; } | ||
| }; | ||
|
|
||
| struct CountDefaultInitializations { | ||
| CountDefaultInitializations() { ++constructions; } | ||
| static int constructions; | ||
| }; | ||
| int CountDefaultInitializations::constructions = 0; | ||
|
|
||
| constexpr bool test() { | ||
| { | ||
| int i = 99; | ||
| int* res = std::construct_at(&i); | ||
| int i = 99; | ||
| std::same_as<int*> auto res = std::construct_at(&i); | ||
| assert(res == &i); | ||
| assert(*res == 0); | ||
| } | ||
|
|
||
| { | ||
| int i = 0; | ||
| int* res = std::construct_at(&i, 42); | ||
| int i = 0; | ||
| std::same_as<int*> auto res = std::construct_at(&i, 42); | ||
| assert(res == &i); | ||
| assert(*res == 42); | ||
| } | ||
|
|
||
| { | ||
| Foo foo = {}; | ||
| int count = 0; | ||
| Foo* res = std::construct_at(&foo, 42, 'x', 123.89, &count); | ||
| Foo foo = {}; | ||
| int count = 0; | ||
| std::same_as<Foo*> auto res = std::construct_at(&foo, 42, 'x', 123.89, &count); | ||
| assert(res == &foo); | ||
| assert(*res == Foo(42, 'x', 123.89)); | ||
| assert(count == 1); | ||
|
|
@@ -78,12 +86,70 @@ constexpr bool test() { | |
| a.deallocate(p, 2); | ||
| } | ||
|
|
||
| // Test LWG3436, std::construct_at with array types | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC Clang didn't support placement new for array in constant evaluation until Clang 20. (The ability was added via #104586.) So, should we skip "Clang 19 + constant evaluation" here? |
||
| { | ||
| { | ||
| using Array = int[1]; | ||
| Array array; | ||
| std::same_as<Array*> auto result = std::construct_at(&array); | ||
| assert(result == &array); | ||
| assert(array[0] == 0); | ||
| } | ||
| { | ||
| using Array = int[2]; | ||
| Array array; | ||
| std::same_as<Array*> auto result = std::construct_at(&array); | ||
| assert(result == &array); | ||
| assert(array[0] == 0); | ||
| assert(array[1] == 0); | ||
| } | ||
| { | ||
| using Array = int[3]; | ||
| Array array; | ||
| std::same_as<Array*> auto result = std::construct_at(&array); | ||
| assert(result == &array); | ||
| assert(array[0] == 0); | ||
| assert(array[1] == 0); | ||
| assert(array[2] == 0); | ||
| } | ||
|
|
||
| // Make sure we initialize the right number of elements. This can't be done inside | ||
| // constexpr since it requires a global variable. | ||
| if (!TEST_IS_CONSTANT_EVALUATED) { | ||
| { | ||
| using Array = CountDefaultInitializations[1]; | ||
| CountDefaultInitializations array[1]; | ||
| CountDefaultInitializations::constructions = 0; | ||
| std::construct_at(&array); | ||
| assert(CountDefaultInitializations::constructions == 1); | ||
| } | ||
| { | ||
| using Array = CountDefaultInitializations[2]; | ||
| CountDefaultInitializations array[2]; | ||
| CountDefaultInitializations::constructions = 0; | ||
| std::construct_at(&array); | ||
| assert(CountDefaultInitializations::constructions == 2); | ||
| } | ||
| { | ||
| using Array = CountDefaultInitializations[3]; | ||
| CountDefaultInitializations array[3]; | ||
| CountDefaultInitializations::constructions = 0; | ||
| std::construct_at(&array); | ||
| assert(CountDefaultInitializations::constructions == 3); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| template <class... Args> | ||
| constexpr bool can_construct_at = requires { std::construct_at(std::declval<Args>()...); }; | ||
|
|
||
| struct NoDefault { | ||
| NoDefault() = delete; | ||
| }; | ||
|
|
||
| // Check that SFINAE works. | ||
| static_assert(can_construct_at<int*, int>); | ||
| static_assert(can_construct_at<Foo*, int, char, double>); | ||
|
|
@@ -96,6 +162,11 @@ static_assert(!can_construct_at<contiguous_iterator<Foo*>, int, char, double>); | |
| static_assert(!can_construct_at<int (*)()>); | ||
| static_assert(!can_construct_at<int (*)(), std::nullptr_t>); | ||
|
|
||
| // LWG3436 | ||
| static_assert(can_construct_at<int (*)[3]>); // test the test | ||
| static_assert(!can_construct_at<int (*)[]>); // unbounded arrays should SFINAE away | ||
| static_assert(!can_construct_at<NoDefault (*)[1]>); // non default constructible shouldn't work | ||
|
|
||
| int main(int, char**) { | ||
| test(); | ||
| static_assert(test()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| // UNSUPPORTED: c++03, c++11, c++14, c++17 | ||
| // REQUIRES: stdlib=libc++ | ||
|
|
||
| // <memory> | ||
|
|
||
| // Test that std::ranges::construct_at provides a meaningful diagnostic when used with an | ||
| // array type and construction arguments are provided. See LWG3436. | ||
|
|
||
| #include <memory> | ||
|
|
||
| using Array = int[3]; | ||
| void test(Array* a) { | ||
| std::ranges::construct_at(a, 1, 2, 3); | ||
| // expected-error-re@*:* {{static assertion failed {{.*}}construction arguments cannot be passed to construct_at with an array type}} | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to use
requires(ditto for `ranges::construct_at)?