-
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?
Conversation
|
Note that this was originally opened as https://reviews.llvm.org/D114649. |
|
@llvm/pr-subscribers-libcxx Author: Louis Dionne (ldionne) ChangesFixes #118335 Full diff: https://github.com/llvm/llvm-project/pull/132283.diff 7 Files Affected:
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index 9bf31c417f3c9..3f1e7fef4520a 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -77,7 +77,7 @@
"`LWG4106 <https://wg21.link/LWG4106>`__","``basic_format_args`` should not be default-constructible","2024-06 (St. Louis)","|Complete|","19",""
"","","","","",""
"`LWG3216 <https://wg21.link/LWG3216>`__","Rebinding the allocator before calling ``construct``/``destroy`` in ``allocate_shared``","2024-11 (Wrocław)","","",""
-"`LWG3436 <https://wg21.link/LWG3436>`__","``std::construct_at`` should support arrays","2024-11 (Wrocław)","","",""
+"`LWG3436 <https://wg21.link/LWG3436>`__","``std::construct_at`` should support arrays","2024-11 (Wrocław)","|Complete|","21",""
"`LWG3886 <https://wg21.link/LWG3886>`__","Monad mo' problems","2024-11 (Wrocław)","","",""
"`LWG3899 <https://wg21.link/LWG3899>`__","``co_yield``\ing elements of an lvalue generator is unnecessarily inefficient","2024-11 (Wrocław)","","",""
"`LWG3900 <https://wg21.link/LWG3900>`__","The ``allocator_arg_t`` overloads of ``generator::promise_type::operator new`` should not be constrained","2024-11 (Wrocław)","","",""
diff --git a/libcxx/include/__memory/construct_at.h b/libcxx/include/__memory/construct_at.h
index 21337e766b2d0..f6f49d6daa0e6 100644
--- a/libcxx/include/__memory/construct_at.h
+++ b/libcxx/include/__memory/construct_at.h
@@ -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>
_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)...);
diff --git a/libcxx/include/__memory/ranges_construct_at.h b/libcxx/include/__memory/ranges_construct_at.h
index b8a94678c10c8..3d98262ec3650 100644
--- a/libcxx/include/__memory/ranges_construct_at.h
+++ b/libcxx/include/__memory/ranges_construct_at.h
@@ -19,6 +19,7 @@
#include <__ranges/access.h>
#include <__ranges/concepts.h>
#include <__ranges/dangling.h>
+#include <__type_traits/is_unbounded_array.h>
#include <__utility/declval.h>
#include <__utility/forward.h>
#include <__utility/move.h>
@@ -38,7 +39,10 @@ namespace ranges {
// construct_at
struct __construct_at {
- 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* operator()(_Tp* __location, _Args&&... __args) const {
return std::construct_at(__location, std::forward<_Args>(__args)...);
}
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.array.verify.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.array.verify.cpp
new file mode 100644
index 0000000000000..e732b75c4dcde
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.array.verify.cpp
@@ -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::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}}
+}
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp
index 00ec287ffeb66..10e3c7fbeecdd 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp
@@ -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
+ {
+ {
+ 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());
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.array.verify.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.array.verify.cpp
new file mode 100644
index 0000000000000..1a1b8db96c8b4
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.array.verify.cpp
@@ -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}}
+}
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.pass.cpp
index 66e6f87432703..91e0597b6a6c6 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.pass.cpp
@@ -16,6 +16,7 @@
// }
#include <cassert>
+#include <concepts>
#include <initializer_list>
#include <memory>
#include <type_traits>
@@ -52,12 +53,18 @@ struct Counted {
constexpr ~Counted() { --count; }
};
+struct CountDefaultInitializations {
+ CountDefaultInitializations() { ++constructions; }
+ static int constructions;
+};
+int CountDefaultInitializations::constructions = 0;
+
constexpr bool test() {
// Value initialization.
{
int x = 1;
- int* result = std::ranges::construct_at(&x);
+ std::same_as<int*> auto result = std::ranges::construct_at(&x);
assert(result == &x);
assert(x == 0);
}
@@ -66,7 +73,7 @@ constexpr bool test() {
{
int x = 1;
- int* result = std::ranges::construct_at(&x, 42);
+ std::same_as<int*> auto result = std::ranges::construct_at(&x, 42);
assert(result == &x);
assert(x == 42);
}
@@ -75,7 +82,7 @@ constexpr bool test() {
{
Foo f;
- Foo* result = std::ranges::construct_at(std::addressof(f), 42, 123);
+ std::same_as<Foo*> auto result = std::ranges::construct_at(std::addressof(f), 42, 123);
assert(result == std::addressof(f));
assert(f.x == 42);
assert(f.y == 123);
@@ -87,7 +94,7 @@ constexpr bool test() {
Counted* out = alloc.allocate(2);
int count = 0;
- Counted* result = std::ranges::construct_at(out, count);
+ std::same_as<Counted*> auto result = std::ranges::construct_at(out, count);
assert(result == out);
assert(count == 1);
@@ -99,28 +106,85 @@ constexpr bool test() {
alloc.deallocate(out, 2);
}
- return true;
-}
+ // Test LWG3436, std::ranges::construct_at with array types
+ {
+ {
+ using Array = int[1];
+ Array array;
+ std::same_as<Array*> auto result = std::ranges::construct_at(&array);
+ assert(result == &array);
+ assert(array[0] == 0);
+ }
+ {
+ using Array = int[2];
+ Array array;
+ std::same_as<Array*> auto result = std::ranges::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::ranges::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::ranges::construct_at(&array);
+ assert(CountDefaultInitializations::constructions == 1);
+ }
+ {
+ using Array = CountDefaultInitializations[2];
+ CountDefaultInitializations array[2];
+ CountDefaultInitializations::constructions = 0;
+ std::ranges::construct_at(&array);
+ assert(CountDefaultInitializations::constructions == 2);
+ }
+ {
+ using Array = CountDefaultInitializations[3];
+ CountDefaultInitializations array[3];
+ CountDefaultInitializations::constructions = 0;
+ std::ranges::construct_at(&array);
+ assert(CountDefaultInitializations::constructions == 3);
+ }
+ }
+ }
-constexpr bool can_construct_at(auto&&... args)
- requires requires { std::ranges::construct_at(decltype(args)(args)...); }
-{
return true;
}
-constexpr bool can_construct_at(auto&&...) { return false; }
+template <class... Args>
+constexpr bool can_construct_at = requires { std::ranges::construct_at(std::declval<Args>()...); };
+
+struct NoDefault {
+ NoDefault() = delete;
+};
// Check that SFINAE works.
-static_assert(can_construct_at((Foo*)nullptr, 1, 2));
-static_assert(!can_construct_at((Foo*)nullptr, 1));
-static_assert(!can_construct_at((Foo*)nullptr, 1, 2, 3));
-static_assert(!can_construct_at(nullptr, 1, 2));
-static_assert(!can_construct_at((int*)nullptr, 1, 2));
-static_assert(!can_construct_at(contiguous_iterator<Foo*>(), 1, 2));
+static_assert(can_construct_at<Foo*, int, int>);
+static_assert(!can_construct_at<Foo*, int>);
+static_assert(!can_construct_at<Foo*, int, int, int>);
+static_assert(!can_construct_at<std::nullptr_t, int, int>);
+static_assert(!can_construct_at<int*, int, int>);
+static_assert(!can_construct_at<contiguous_iterator<Foo*>, int, int>);
// Can't construct function pointers.
-static_assert(!can_construct_at((int (*)()) nullptr));
-static_assert(!can_construct_at((int (*)()) nullptr, nullptr));
-// TODO(varconst): check that array types work once D114649 implementing LWG3639 lands.
+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();
|
|
I think this is breaking in all kinds of ways because we're still missing some Clang support for arrays in placement-new expressions. I wasn't able to get my tests fully passing locally. |
| 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> |
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.
is_unbounded_array_v is introduced in C++20 so we shouldn't use it for __construct_at. Moreover, should we make the internal __construct_at have the same SFINAE constraints as std::construct_at?
| class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...)), | ||
| __enable_if_t<!is_unbounded_array_v<_Tp>, int> = 0> |
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)?
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| // UNSUPPORTED: c++03, c++11, c++14, c++17 |
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.
It seems that we can use the new style (ditto below):
| // UNSUPPORTED: c++03, c++11, c++14, c++17 | |
| // REQUIRES: std-at-least-c++20 |
| a.deallocate(p, 2); | ||
| } | ||
|
|
||
| // Test LWG3436, std::construct_at with array types |
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.
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?
Fixes #118335