std::construct_at
should support arraysSection: 26.11.8 [specialized.construct] Status: Ready Submitter: Jonathan Wakely Opened: 2020-04-29 Last modified: 2024-06-24
Priority: 2
View other active issues in [specialized.construct].
View all other issues in [specialized.construct].
View all issues with Ready status.
Discussion:
std::construct_at
is ill-formed for array types, because the type of the new
-expression is T
not T*
so it cannot be converted to the return type.
allocator_traits::construct
did work for arrays, because it returns void
so there is no
ill-formed conversion. On the other hand, in C++17 allocator_traits::destroy
didn't work for arrays,
because p->~T()
isn't valid.
In C++20 allocator_traits::destroy
does work, because std::destroy_at
treats arrays specially,
but allocator_traits::construct
no longer works because it uses std::construct_at
.
It seems unnecessary and/or confusing to remove support for arrays in construct
when we're adding it in destroy
.
I suggest that std::construct_at
should also handle arrays. It might be reasonable to restrict that
support to the case where sizeof...(Args) == 0
, if supporting parenthesized aggregate-initialization
is not desirable in std::construct_at
.
[2020-05-09; Reflector prioritization]
Set priority to 2 after reflector discussions.
[2021-01-16; Zhihao Yuan provides wording]
Previous resolution [SUPERSEDED]:
This wording is relative to N4878.
Modify 26.11.8 [specialized.construct] as indicated:
template<class T, class... Args> constexpr T* construct_at(T* location, Args&&... args); namespace ranges { template<class T, class... Args> constexpr T* construct_at(T* location, Args&&... args); }-1- Constraints: The expression
-2- Effects: Equivalent to:::new (declval<void*>()) T(declval<Args>()...)
is well-formed when treated as an unevaluated operand.returnauto ptr = ::new (voidify(*location)) T(std::forward<Args>(args)...); if constexpr (is_array_v<T>) return launder(location); else return ptr;
[2021-12-07; Zhihao Yuan comments and provides improved wording]
The previous PR allows constructing arbitrary number of elements when
T
is an array of unknown bound:
extern int a[]; std::construct_at(&a, 0, 1, 2);
and leads to a UB.
Previous resolution [SUPERSEDED]:
This wording is relative to N4901.
Modify 26.11.8 [specialized.construct] as indicated:
template<class T, class... Args> constexpr T* construct_at(T* location, Args&&... args); namespace ranges { template<class T, class... Args> constexpr T* construct_at(T* location, Args&&... args); }-1- Constraints: The expression
-2- Effects: Equivalent to:::new (declval<void*>()) T(declval<Args>()...)
is well-formed when treated as an unevaluated operand (7.2.3 [expr.context]) andis_unbounded_array_v<T>
isfalse
.returnauto ptr = ::new (voidify(*location)) T(std::forward<Args>(args)...); if constexpr (is_array_v<T>) return launder(location); else return ptr;
[2024-03-18; Jonathan provides new wording]
During Core review in Varna, Hubert suggested creating T[1]
for the array case.
Previous resolution [SUPERSEDED]:
This wording is relative to N4971.
Modify 26.11.8 [specialized.construct] as indicated:
template<class T, class... Args> constexpr T* construct_at(T* location, Args&&... args); namespace ranges { template<class T, class... Args> constexpr T* construct_at(T* location, Args&&... args); }-1- Constraints:
-2- Effects: Equivalent to:is_unbounded_array_v<T>
isfalse
. The expression::new (declval<void*>()) T(declval<Args>()...)
is well-formed when treated as an unevaluated operand (7.2.3 [expr.context]).if constexpr (is_array_v<T>) return ::new (voidify(*location)) T[1]{{std::forward<Args>(args)...}}; else return ::new (voidify(*location)) T(std::forward<Args>(args)...);
[St. Louis 2024-06-24; Jonathan provides improved wording]
Why not support unbounded arrays, deducing the bound from sizeof...(Args)
?
JW: There's no motivation to support that here in construct_at
.
It isn't possible to create unbounded arrays via allocators,
nor via any of the uninitialized_xxx
algorithms. Extending construct_at
that way seems like a design change, not restoring support for something
that used to work with allocators and then got broken in C++20.
Tim observed that the proposed resolution is ill-formed if T
has an
explicit default constructor. Value-initialization would work for that case,
and there seems to be little motivation for supplying arguments to
initialize the array. In C++17 the allocator_traits::construct
case only
supported value-initialization.
[St. Louis 2024-06-24; move to Ready.]
Proposed resolution:
This wording is relative to N4981.
Modify 26.11.8 [specialized.construct] as indicated:
template<class T, class... Args> constexpr T* construct_at(T* location, Args&&... args); namespace ranges { template<class T, class... Args> constexpr T* construct_at(T* location, Args&&... args); }-1- Constraints:
is_unbounded_array_v<T>
isfalse
. The expression::new (declval<void*>()) T(declval<Args>()...)
is well-formed when treated as an unevaluated operand (7.2.3 [expr.context]).-?- Mandates: If
is_array_v<T>
istrue
,sizeof...(Args)
is zero.-2- Effects: Equivalent to:
if constexpr (is_array_v<T>) return ::new (voidify(*location)) T[1](); else return ::new (voidify(*location)) T(std::forward<Args>(args)...);