Search code examples
c++curly-bracesstdarraylist-initialization

Double braces required for list-initialization of container std::array


List-initialization of containers of user-defined types does not behave as I would expect. See this snippet:

#include <array>

struct A {
    char C;
    int s;
};

int main(int argc, char * argv[]) {
    A x = {'x'}, y = {'y'};

    std::array<int, 2> i = {1, 2}; // Ok

    std::array<A, 2> a = {x, y}; // Ok
    //std::array<A, 2> b =  { {'x',1000}, {'y',2000} }; // Error: too many initializers!!!
    std::array<A, 2> c = { A{'x',1000}, A{'y',1000} };
    std::array<A, 2> d = {{ {'x',1000}, {'y',1000} }}; // Ok!!
    std::array<A, 2> e = {'x', 2000, 'y', 5000}; // Ok!!
} 

I can initialize i as if it were a fundamental array. I can do the same with a, as long as they are variables. But I cannot initialize b without specifying the type A, such as in c.

  1. To initialize an std::array without explicitly stating the type A, I have to add another pair of braces. What is the logic behind the need for double braces? Why can it not be initialized with a single pair of braces surrounding the list, as in b?

  2. Also, surprisingly c works and it only yields two objects! Intuitively, I would expect that e would yield error because there are 4 initial values for a maximum of 2 objects, but instead the compiler fills in the members of A correctly! Why does this happen?


Solution

  • Braces, nested however deeply, are matched against the structure of the object being initialized before considering any type information. Since std::array<T,N> must contain a true T[N] (rather than be one), the structure is that there is one object inside the array—that is, the array. Two opening braces are therefore taken to begin the initializer for that array, which doesn’t work if the entire nested set is needed to initialize one array element, nor if there is more than one such nested set.

    When some initializer clause is an expression, even A{…}, this decomposition stops and the initializer is used for one subobject. However, if that expression cannot be converted to the type of the appropriate subobject and that type is itself an aggregate, brace elision takes place and the subsequent initializers are used for its subobjects despite the lack of braces. When that applies to the T[N] object itself, the array may be initialized with only one layer of braces.

    So, in short, an open brace causes decomposition of an aggregate, whether it works or not, but a type mismatch also causes decomposition of an aggregate.