std::array<T,0>
initialization work when T
is not default-constructible?Section: 23.3.3.5 [array.zero] Status: Open Submitter: Daryle Walker Opened: 2012-05-08 Last modified: 2021-03-14
Priority: 3
View all other issues in [array.zero].
View all issues with Open status.
Discussion:
Objects of std::array<T, N>
are supposed to be initialized with aggregate initialization (when
not the destination of a copy or move). This clearly works when N
is positive. What happens when N
is zero? To continue using an (inner) set of braces for initialization, a std::array<T, 0>
implementation
must have an array member of at least one element, and let default initialization take care of those secret elements.
This cannot work when T
has a set of constructors and the default constructor is deleted from that set.
Solution: Add a new paragraph in 23.3.3.5 [array.zero]:
The unspecified internal structure of array for this case shall allow initializations like:
array<T, 0> a = { };and said initializations must be valid even when
T
is not default-constructible.
[2012, Portland: Move to Open]
Some discussion to understand the issue, which is that implementations currently have freedom to implement
an empty array
by holding a dummy element, and so might not support value initialization, which is
surprising when trying to construct an empty container. However, this is not mandated, it is an unspecified
implementation detail.
Jeffrey points out that the implication of 23.3.3.1 [array.overview] is that this initialization syntax
must be supported by empty array
objects already. This is a surprising inference that was not
obvious to the room, but consensus is that the reading is accurate, so the proposed resolution is not necessary,
although the increased clarity may be useful.
Further observation is that the same clause effectively implies that T
must always be DefaultConstructible,
regardless of N
for the same reasons - as an initializer-list may not supply enough values, and the
remaining elements must all be value initialized.
Concern that we are dancing angels on the head of pin, and that relying on such subtle implications in wording is not helpful. We need a clarification of the text in this area, and await wording.
[2015-02 Cologne]
DK: What was the outcome of Portland? AM: Initially we thought we already had the intended behaviour.
We concluded that T
must always be DefaultConstructible
, but I'm not sure why. GR: It's p2 in
std::array
, "up to N
". AM: That wording already implies that "{}
" has to work when N
is zero. But the wording of p2 needs to be fixed to make clear that it does not imply that T
must be
DefaultConstructible
.
[2015-10, Kona Saturday afternoon]
MC: How important is this? Can you not just use default construction for empty arrays?
TK: It needs to degenerate properly from a pack. STL agrees.
JW: Yes, this is important, and we have to make it work.
MC: I hate the words "initialization like".
JW: I'll reword this.
WEB: Can I ask that once JW has reworded this we move it to Review rather than Open?
MC: We'll try to review it in a telecon and hopefully get it to tentatively ready.
STL: Double braces must also work: array<T, 0> a = {{}};
.
Jonathan to reword.
[2018-03-14 Wednesday evening issues processing]
Jens suggested that we remove the requirement that begin() == end() ==
unique-value,
specifically the unique value part.
Previous resolution [SUPERSEDED]:
This wording is relative to N3376.
Add the following new paragraph between the current 23.3.3.5 [array.zero] p1 and p2:
-1-
-?- The unspecified internal structure ofarray
shall provide support for the special caseN == 0
.array
for this case shall allow initializations like:array<T, 0> a = { };and said initializations must be valid even when
-2- In the case thatT
is not default-constructible.N == 0
,begin() == end() ==
unique value. The return value ofdata()
is unspecified. -3- The effect of callingfront()
orback()
for a zero-sized array is undefined. -4- Member functionswap()
shall have a noexcept-specification which is equivalent tonoexcept(true)
.
[2018-06-14, Jonathan Wakely comments and provides revised wording]
The new wording does not address the 2018-03-14 suggestion from Jens to remove the unique value. It wasn't clear to me that there was consensus to make that change, and it would be a change in behaviour not just a clarification of the existing wording.
Previous resolution [SUPERSEDED]:
This wording is relative to N4750.
Modify 23.3.3.5 [array.zero] as indicated:
-1-
-?- A zero-sizedarray
shallprovides support for the special case of a zero-sizedarray
that is always empty, i.e.N == 0
, with the properties described in this subclause.array
type is an aggregate that meets theDefaultConstructible
(Table 22) andCopyConstructible
(Table 24) requirements. There is a single element of the aggregate, of an unspecifiedDefaultConstructible
type. [Note: This allows initialization of the formarray<T, 0> a = {{}};
. There is no requirement forT
to beDefaultConstructible
. — end note] -2-In the case thatN == 0
,begin() == end() ==
unique valuebegin()
andend()
return non-dereferenceable iterators such thatbegin() == end()
anda.begin() != b.begin()
wherea
andb
are distinct objects of the same zero-sizedarray
type. The return value ofdata()
is unspecified. -3- The effect of callingfront()
orback()
for a zero-sized array is undefined. -4- Member functionswap()
shall havehas constant complexity and a non-throwing exception specification.
[2018-08-30, Jonathan revises wording following feedback from Daniel Kruegler and Tim Song.]
Daniel noted that it's undefined to compare iterators from different containers,
so a.begin() != b.begin()
can't be used. That means whether the iterators
from different containers are unique is unobservable anyway.
We can say they don't share the same underlying sequence, which tells users they can't compare them
and tells implementors they can't return value-initialized iterators.
Tim noted that it's not sufficient to say the unspecified type in a zero-sized array is DefaultConstructible,
it also needs to be constructible from = {}
. Also, a zero-sized array should be CopyAssignable.
Previous resolution [SUPERSEDED]:
This wording is relative to N4762.
Modify 23.3.3.5 [array.zero] as indicated:
-1-
-?- A zero-sizedarray
shallprovides support for the special case of a zero-sizedarray
that is always empty, i.e.N == 0
, with the properties described in this subclause.array
type is an aggregate that meets the Cpp17DefaultConstructible (Table 24) and Cpp17CopyConstructible (Table 26) and Cpp17CopyAssignable (Table 28) requirements. There is a single element of the aggregate, of an unspecified Cpp17DefaultConstructible type that is copy-list-initializable from an empty list. [Note: This allows initialization of the formarray<T, 0> a = {{}};
. There is no requirement forT
to be Cpp17DefaultConstructible. — end note] -2-In the case thatN == 0
,begin() == end() ==
unique valuebegin()
andend()
return non-dereferenceable iterators such thatbegin() == end()
. Whena
andb
are distinct objects of the same zero-sizedarray
type,a.begin()
andb.begin()
are not iterators over the same underlying sequence. [Note: Thereforebegin()
does not return a value-initialized iterator — end note]. The return value ofdata()
is unspecified. -3- The effect of callingfront()
orback()
for a zero-sized array is undefined. -4- Member functionswap()
shall havehas constant complexity and a non-throwing exception specification.
[2021-03-14; Johel Ernesto Guerrero Peña comments and provides improved wording]
The currently proposed wording specifies:
There is a single element of the aggregate, of an unspecified Cpp17DefaultConstructible type that is copy-list-initializable from an empty list.
This doesn't specify which expressions involving zero-sized array specializations are constant expressions.
23.3.3.1 [array.overview] p4 specifies array<T, 0>
to be a structural type when T
is a structural type. This requires that its single element, let's call it single-element, be a
structural type. But that says nothing about which of the special member functions of single-element
are constant expressions. By being a structural type, single-element is permitted to be implemented
as a literal class type. To meet this requirement, single-element can be implemented to have one
constexpr constructor that is not a copy or move constructor (6.8.1 [basic.types.general] p10), so
its default constructor needn't be constexpr. This is unlike non-zero-sized array specializations, which
inherit these properties from T
. Furthermore, this permits an implementation of single-element
whose default constructor stores the result of std::source_location::current()
in a data member
(as exemplified in the specification for current
). Cpp17DefaultConstructible doesn't
require the default constructor to produce equal values. The simplest way to solve these issues and any other
that might arise from future changes and oversights would be to specify single-element as an empty
aggregate type. Then the wording from 23.3.3.2 [array.cons] p1 makes it clear that all the special
member functions are constant expressions. It would also mean that the default constructor produces
template-argument-equivalent values.
Proposed resolution:
This wording is relative to N4878.
Modify 23.3.3.5 [array.zero] as indicated:
-1-
-?- A zero-sizedarray
shallprovides support for the special case of a zero-sizedarray
that is always empty, i.e.N == 0
, with the properties described in this subclause.array
type is an aggregate that meets the Cpp17DefaultConstructible (Table 29 [tab:cpp17.defaultconstructible]) and Cpp17CopyConstructible (Table 31 [tab:cpp17.copyconstructible]) and Cpp17CopyAssignable (Table 33 [tab:cpp17.copyassignable]) requirements. There is a single element of the aggregate, of an unspecified empty aggregate type. [Note: This allows initialization of the formarray<T, 0> a = {{}};
. There is no requirement forT
to be Cpp17DefaultConstructible. — end note] -2-In the case thatN == 0
,begin() == end() ==
unique valuebegin()
andend()
return non-dereferenceable iterators such thatbegin() == end()
. Whena
andb
are distinct objects of the same zero-sizedarray
type,a.begin()
andb.begin()
are not iterators over the same underlying sequence. [Note: Thereforebegin()
does not return a value-initialized iterator — end note].. The return value ofdata()
is unspecified. -3- The effect of callingfront()
orback()
for a zero-sized array is undefined. -4- Member functionswap()
shall havehas constant complexity and a non-throwing exception specification.