The extra braces are needed because std::array
is an aggregate and Plain Old Datatype (POD), unlike other containers in the standard library. std::array
doesn't have a user-defined constructor. Its first data member is an array of size N
(which you pass as a template argument), and this member is directly initialized with an initializer. The extra braces are needed for the internal array which is being directly initialized.
The situation is same as:
//define this aggregate - no user-defined constructor
struct Aarray
{
A data[2]; //data is an internal array
};
How would you initialize this? If you do this:
Aarray a1 =
{
{0, 0.1},
{2, 3.4}
};
it gives a compilation error:
error: too many initializers for 'Aarray'
This is the same error which you get in the case of a std::array
(if you use GCC).
So the correct thing to do is to use braces as follows:
Aarray a1 =
{
{ //<--this tells the compiler that initialization of `data` starts
{ //<-- initialization of `data[0]` starts
0, 0.1
}, //<-- initialization of `data[0]` ends
{2, 3.4} //initialization of data[1] starts and ends, as above
} //<--this tells the compiler that initialization of `data` ends
};
which compiles fine. Once again, the extra braces are needed because you're initializing the internal array.
--
Now the question is why are extra braces not needed in case of double
?
It is because double
is not an aggregate, while A
is. In other words, std::array<double, 2>
is an aggregate of aggregate, while std::array<A, 2>
is an aggregate of aggregate of aggregate1.
1. I think that extra braces are still needed in the case of double also (like this), to be completely conformant to the Standard, but the code works without them. It seems I need to dig through the spec again!.
#More on braces and extra braces
I dug through the spec. This section (§8.5.1/11 from C++11) is interesting and applies to this case:
In a declaration of the form
T x = { a };
braces can be elided in an initializer-list as follows. If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the members of a subaggregate; it is erroneous for there to be more initializer-clauses than members. If, however, the initializer-list for a subaggregate
does not begin with a left brace, then only enough initializer-clauses from the list are taken to initialize the members of the subaggregate; any remaining initializer-clauses are left to initialize the next member of the aggregate of which the current subaggregate is a member. [ Example:
float y[4][3] = {
{ 1, 3, 5 },
{ 2, 4, 6 },
{ 3, 5, 7 },
};
is a completely-braced initialization: 1, 3, and 5 initialize the first row of the array y[0]
, namely y[0][0]
, y[0][1]
, and y[0][2]
. Likewise the next two lines initialize y[1]
and y[2]
. The initializer ends early and therefore y[3]s
elements are initialized as if explicitly initialized with an expression of the form float(), that is, are initialized with 0.0. In the following example, braces in the initializer-list are elided; however the initializer-list has the same effect as the completely-braced initializer-list of the above example,
float y[4][3] = {
1, 3, 5, 2, 4, 6, 3, 5, 7
};
The initializer for y begins with a left brace, but the one for y[0]
does not, therefore three elements from the list are used. Likewise the next three are taken successively for y[1]
and y[2]
. —end example ]
Based on what I understood from the above quote, I can say that the following should be allowed:
//OKAY. Braces are completely elided for the inner-aggregate
std::array<A, 2> X =
{
0, 0.1,
2, 3.4
};
//OKAY. Completely-braced initialization
std::array<A, 2> Y =
{{
{0, 0.1},
{2, 3.4}
}};
In the first one, braces for the inner-aggregate are completely elided, while the second has fully-braced initialization. In your case (the case of double
), the initialization uses the first approach (braces are completely elided for the inner aggregate).
But this should be disallowed:
//ILL-FORMED : neither braces-elided, nor fully-braced
std::array<A, 2> Z =
{
{0, 0.1},
{2, 3.4}
};
It is neither braces-elided, nor are there enough braces to be completely-braced initialization. Therefore, it is ill-formed.
double
? Aren't you allowed to collapse braces under certain conditions? Or maybe that was only true for multi-dimensional arrays... – Hangmandouble
is not an aggregate, whileA
is. In other words,std::array<double, 2>
is an aggregate of aggregate, whilestd::array<A, 2>
is an aggregate of aggregate of aggregate! – Howarddouble
array with one pair of braces, as opposed to three for theA
type. We're still one pair short! – Hangmandouble
also, to be completely the Standard conformant, but it works without it. It seems I need to dig the spec again! – Howardstd::array<double, 2> a2 = {{0.1, 2.3}};
, but I wonder too why it's allowed to omit one pair of braces. – Santamaria