Skip to content

Commit bdfbdca

Browse files
committed
[libc++] Implement LWG3436: support for arrays in std::construct_at
Fixes #118335
1 parent be36f41 commit bdfbdca

File tree

7 files changed

+228
-31
lines changed

7 files changed

+228
-31
lines changed

libcxx/docs/Status/Cxx2cIssues.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"`LWG4106 <https://wg21.link/LWG4106>`__","``basic_format_args`` should not be default-constructible","2024-06 (St. Louis)","|Complete|","19",""
7878
"","","","","",""
7979
"`LWG3216 <https://wg21.link/LWG3216>`__","Rebinding the allocator before calling ``construct``/``destroy`` in ``allocate_shared``","2024-11 (Wrocław)","","",""
80-
"`LWG3436 <https://wg21.link/LWG3436>`__","``std::construct_at`` should support arrays","2024-11 (Wrocław)","","",""
80+
"`LWG3436 <https://wg21.link/LWG3436>`__","``std::construct_at`` should support arrays","2024-11 (Wrocław)","|Complete|","21",""
8181
"`LWG3886 <https://wg21.link/LWG3886>`__","Monad mo' problems","2024-11 (Wrocław)","","",""
8282
"`LWG3899 <https://wg21.link/LWG3899>`__","``co_yield``\ing elements of an lvalue generator is unnecessarily inefficient","2024-11 (Wrocław)","","",""
8383
"`LWG3900 <https://wg21.link/LWG3900>`__","The ``allocator_arg_t`` overloads of ``generator::promise_type::operator new`` should not be constrained","2024-11 (Wrocław)","","",""

libcxx/include/__memory/construct_at.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <__new/placement_new_delete.h>
1818
#include <__type_traits/enable_if.h>
1919
#include <__type_traits/is_array.h>
20+
#include <__type_traits/is_unbounded_array.h>
2021
#include <__utility/declval.h>
2122
#include <__utility/forward.h>
2223
#include <__utility/move.h>
@@ -34,15 +35,26 @@ _LIBCPP_BEGIN_NAMESPACE_STD
3435

3536
#if _LIBCPP_STD_VER >= 20
3637

37-
template <class _Tp, class... _Args, class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...))>
38+
template <class _Tp,
39+
class... _Args,
40+
class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...)),
41+
__enable_if_t<!is_unbounded_array_v<_Tp>, int> = 0>
3842
_LIBCPP_HIDE_FROM_ABI constexpr _Tp* construct_at(_Tp* __location, _Args&&... __args) {
3943
_LIBCPP_ASSERT_NON_NULL(__location != nullptr, "null pointer given to construct_at");
40-
return ::new (static_cast<void*>(__location)) _Tp(std::forward<_Args>(__args)...);
44+
if constexpr (is_array_v<_Tp>) {
45+
static_assert(sizeof...(_Args) == 0, "construction arguments cannot be passed to construct_at with an array type");
46+
return ::new (static_cast<void*>(__location)) _Tp[1]();
47+
} else {
48+
return ::new (static_cast<void*>(__location)) _Tp(std::forward<_Args>(__args)...);
49+
}
4150
}
4251

4352
#endif
4453

45-
template <class _Tp, class... _Args, class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...))>
54+
template <class _Tp,
55+
class... _Args,
56+
class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...)),
57+
__enable_if_t<!is_unbounded_array_v<_Tp>, int> = 0>
4658
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp* __construct_at(_Tp* __location, _Args&&... __args) {
4759
#if _LIBCPP_STD_VER >= 20
4860
return std::construct_at(__location, std::forward<_Args>(__args)...);

libcxx/include/__memory/ranges_construct_at.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <__ranges/access.h>
2020
#include <__ranges/concepts.h>
2121
#include <__ranges/dangling.h>
22+
#include <__type_traits/is_unbounded_array.h>
2223
#include <__utility/declval.h>
2324
#include <__utility/forward.h>
2425
#include <__utility/move.h>
@@ -38,7 +39,10 @@ namespace ranges {
3839
// construct_at
3940

4041
struct __construct_at {
41-
template <class _Tp, class... _Args, class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...))>
42+
template <class _Tp,
43+
class... _Args,
44+
class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...)),
45+
__enable_if_t<!is_unbounded_array_v<_Tp>, int> = 0>
4246
_LIBCPP_HIDE_FROM_ABI constexpr _Tp* operator()(_Tp* __location, _Args&&... __args) const {
4347
return std::construct_at(__location, std::forward<_Args>(__args)...);
4448
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17
10+
// REQUIRES: stdlib=libc++
11+
12+
// <memory>
13+
14+
// Test that std::construct_at provides a meaningful diagnostic when used with an
15+
// array type and construction arguments are provided. See LWG3436.
16+
17+
#include <memory>
18+
19+
using Array = int[3];
20+
void test(Array* a) {
21+
std::construct_at(a, 1, 2, 3);
22+
// expected-error-re@*:* {{static assertion failed {{.*}}construction arguments cannot be passed to construct_at with an array type}}
23+
}

libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
// constexpr T* construct_at(T* location, Args&& ...args);
1515

1616
#include <cassert>
17+
#include <concepts>
1718
#include <cstddef>
1819
#include <memory>
1920
#include <utility>
2021

2122
#include "test_iterators.h"
23+
#include "test_macros.h"
2224

2325
struct Foo {
2426
constexpr Foo() {}
@@ -39,25 +41,31 @@ struct Counted {
3941
constexpr ~Counted() { --count_; }
4042
};
4143

44+
struct CountDefaultInitializations {
45+
CountDefaultInitializations() { ++constructions; }
46+
static int constructions;
47+
};
48+
int CountDefaultInitializations::constructions = 0;
49+
4250
constexpr bool test() {
4351
{
44-
int i = 99;
45-
int* res = std::construct_at(&i);
52+
int i = 99;
53+
std::same_as<int*> auto res = std::construct_at(&i);
4654
assert(res == &i);
4755
assert(*res == 0);
4856
}
4957

5058
{
51-
int i = 0;
52-
int* res = std::construct_at(&i, 42);
59+
int i = 0;
60+
std::same_as<int*> auto res = std::construct_at(&i, 42);
5361
assert(res == &i);
5462
assert(*res == 42);
5563
}
5664

5765
{
58-
Foo foo = {};
59-
int count = 0;
60-
Foo* res = std::construct_at(&foo, 42, 'x', 123.89, &count);
66+
Foo foo = {};
67+
int count = 0;
68+
std::same_as<Foo*> auto res = std::construct_at(&foo, 42, 'x', 123.89, &count);
6169
assert(res == &foo);
6270
assert(*res == Foo(42, 'x', 123.89));
6371
assert(count == 1);
@@ -78,12 +86,70 @@ constexpr bool test() {
7886
a.deallocate(p, 2);
7987
}
8088

89+
// Test LWG3436, std::construct_at with array types
90+
{
91+
{
92+
using Array = int[1];
93+
Array array;
94+
std::same_as<Array*> auto result = std::construct_at(&array);
95+
assert(result == &array);
96+
assert(array[0] == 0);
97+
}
98+
{
99+
using Array = int[2];
100+
Array array;
101+
std::same_as<Array*> auto result = std::construct_at(&array);
102+
assert(result == &array);
103+
assert(array[0] == 0);
104+
assert(array[1] == 0);
105+
}
106+
{
107+
using Array = int[3];
108+
Array array;
109+
std::same_as<Array*> auto result = std::construct_at(&array);
110+
assert(result == &array);
111+
assert(array[0] == 0);
112+
assert(array[1] == 0);
113+
assert(array[2] == 0);
114+
}
115+
116+
// Make sure we initialize the right number of elements. This can't be done inside
117+
// constexpr since it requires a global variable.
118+
if (!TEST_IS_CONSTANT_EVALUATED) {
119+
{
120+
using Array = CountDefaultInitializations[1];
121+
CountDefaultInitializations array[1];
122+
CountDefaultInitializations::constructions = 0;
123+
std::construct_at(&array);
124+
assert(CountDefaultInitializations::constructions == 1);
125+
}
126+
{
127+
using Array = CountDefaultInitializations[2];
128+
CountDefaultInitializations array[2];
129+
CountDefaultInitializations::constructions = 0;
130+
std::construct_at(&array);
131+
assert(CountDefaultInitializations::constructions == 2);
132+
}
133+
{
134+
using Array = CountDefaultInitializations[3];
135+
CountDefaultInitializations array[3];
136+
CountDefaultInitializations::constructions = 0;
137+
std::construct_at(&array);
138+
assert(CountDefaultInitializations::constructions == 3);
139+
}
140+
}
141+
}
142+
81143
return true;
82144
}
83145

84146
template <class... Args>
85147
constexpr bool can_construct_at = requires { std::construct_at(std::declval<Args>()...); };
86148

149+
struct NoDefault {
150+
NoDefault() = delete;
151+
};
152+
87153
// Check that SFINAE works.
88154
static_assert(can_construct_at<int*, int>);
89155
static_assert(can_construct_at<Foo*, int, char, double>);
@@ -96,6 +162,11 @@ static_assert(!can_construct_at<contiguous_iterator<Foo*>, int, char, double>);
96162
static_assert(!can_construct_at<int (*)()>);
97163
static_assert(!can_construct_at<int (*)(), std::nullptr_t>);
98164

165+
// LWG3436
166+
static_assert(can_construct_at<int (*)[3]>); // test the test
167+
static_assert(!can_construct_at<int (*)[]>); // unbounded arrays should SFINAE away
168+
static_assert(!can_construct_at<NoDefault (*)[1]>); // non default constructible shouldn't work
169+
99170
int main(int, char**) {
100171
test();
101172
static_assert(test());
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17
10+
// REQUIRES: stdlib=libc++
11+
12+
// <memory>
13+
14+
// Test that std::ranges::construct_at provides a meaningful diagnostic when used with an
15+
// array type and construction arguments are provided. See LWG3436.
16+
17+
#include <memory>
18+
19+
using Array = int[3];
20+
void test(Array* a) {
21+
std::ranges::construct_at(a, 1, 2, 3);
22+
// expected-error-re@*:* {{static assertion failed {{.*}}construction arguments cannot be passed to construct_at with an array type}}
23+
}

0 commit comments

Comments
 (0)