Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When can outer braces be omitted in an initializer list?

I've got error C2078 in VC2010 when compiling the code below.

struct A   {   int foo;   double bar;   };  std::array<A, 2> a1 =    // error C2078: too many initializers   {     {0, 0.1},     {2, 3.4}   };  // OK std::array<double, 2> a2 = {0.1, 2.3}; 

I found out that the correct syntax for a1 is

std::array<A, 2> a1 =    {{     {0, 0.1},     {2, 3.4}   }}; 

The question is: why extra braces are required for a1 but not required for a2?

Update

The question seems to be not specific to std::array. Some examples:

struct B   {   int foo[2];   };  // OK B meow1 = {1,2}; B bark1 = {{1,2}};  struct C   {   struct      {      int a, b;      } foo;   };  // OK C meow2 = {1,2}; C bark2 = {{1,2}};  struct D   {   struct      {      int a, b;      } foo[2];   };  D meow3 = {{1,2},{3,4}};  // error C2078: too many initializers D bark3 = {{{1,2},{3,4}}}; 

I still don't see why struct D gives the error but B and C don't.

like image 239
Andrey Avatar asked Jul 31 '12 07:07

Andrey


1 Answers

The extra braces are needed because std::array is an aggregate and 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.

like image 60
Nawaz Avatar answered Oct 12 '22 01:10

Nawaz