54

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.

Manjabes
  • 1,884
  • 3
  • 17
  • 34
Andriy
  • 8,486
  • 3
  • 27
  • 51
  • @KerrekSB: That will not work! See my answer to know the reason. – Nawaz Jul 31 '12 at 07:29
  • @Nawaz: Then why does it work for `double`? Aren't you allowed to collapse braces under certain conditions? Or maybe that was only true for multi-dimensional arrays... – Kerrek SB Jul 31 '12 at 07:35
  • 1
    @KerrekSB: Because `double` is not an aggregate, while `A` is. In other words, `std::array` is an aggregate of aggregate, while `std::array` is an aggregate of aggregate of aggregate! – Nawaz Jul 31 '12 at 07:37
  • @Nawaz: But note that the OP can do the `double` array with *one* pair of braces, as opposed to *three* for the `A` type. We're still one pair short! – Kerrek SB Jul 31 '12 at 07:40
  • @KerrekSB: I think that extra braces is still needed in case of `double` also, to be completely the Standard conformant, but it works without it. It seems I need to dig the spec again! – Nawaz Jul 31 '12 at 07:44
  • 1
    @Nawaz: A fully standard-conformant syntax for a2 is `std::array a2 = {{0.1, 2.3}};`, but I wonder too why it's allowed to omit one pair of braces. – Andriy Jul 31 '12 at 07:52
  • @Andrey: That is what I said. +1 – Nawaz Jul 31 '12 at 07:53
  • Just as a speculation, std::array might have an operator= that takes an std::initializer_list? – Tamás Szelei Jul 31 '12 at 08:01
  • @fish: certainly not the case in VC2010 – Andriy Jul 31 '12 at 08:06
  • 1
    @fish no, std::array is an aggregate, so it can have no such constructor. The problem is that it may be implemented as having a single array as element, which is why the extra braces are required. – juanchopanza Jul 31 '12 at 08:07
  • There is an interesting, related post [here](http://stackoverflow.com/questions/11400090/c-why-initializer-list-behavior-for-stdvector-and-stdarray-are-different). – juanchopanza Jul 31 '12 at 08:11
  • @Andrey: See the edit/quote in my answer. – Nawaz Jul 31 '12 at 08:26

1 Answers1

68

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.

Alan
  • 1,889
  • 2
  • 18
  • 30
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    An interesting, related post [here](http://stackoverflow.com/questions/11400090/c-why-initializer-list-behavior-for-stdvector-and-stdarray-are-different). To me it is not clear at all that the array **has** to be implemented as a single-element aggregate. The standard provides an example implementation to emphasize that it is an aggregate, but I see no requirement that it be implemented so (although I cannot currently think of an alternative implementation). – juanchopanza Jul 31 '12 at 08:11
  • 1
    Great answer! Just found more compilable initializers for `struct D` from the question: `D d1 = {1,2,3,4}; D d2 = {{1,2,3,4}};` – Andriy Jul 31 '12 at 08:30
  • 3
    For those not clicking the link @juanchopanza posted, there is link to an [official defect report](http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2012/n3367.html#1270) about the brace elision limitation. – Tamás Szelei Jul 31 '12 at 08:36
  • 1
    It is not necessary for a type to be a POD for brace elision to apply. Furthermore, something like `std::array` is plainly *not* a POD. – Luc Danton Aug 01 '12 at 09:29