c++c++11visual-c++-2010

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.


Solution

  • 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.