c++structnesteduniform-initialization

Why do I not need 3 levels of braces for initializing 3 levels of arrays?


I came across to this example

struct sct
{
    int t[2];
};

struct str
{
    sct t[2];
};

int main()
{
    str t[2] = { {0, 2, 4, 6}, {1, 3, 5, 7} }; //Who does this work?

    cout << t[1].t[0].t[1] << t[0].t[1].t[0];     

    return 0;
}

This compiles and runs fine. It gives the output 34

I expected the syntax for initialization would be:

str t[2] = {  { {0, 2},{4, 6} }, { {1, 3},{5, 7} }   };

Instead of

 { {0, 2, 4, 6}, {1, 3, 5, 7} };

But this gave:

In function 'int main()':
error: too many initializers for 'str'

Can someone explain why?

This is a picture to illustrate the way I see this: enter image description here

How should i think when it comes to initializing nested structs?


Solution

  • This looks like a simple typo but the situation is complex enough to tackle it step by step.

    First let me show the solution that seems to work:

    int main()
    {
        str t[2] = { { { {0, 2}, {4, 6} } }, { { {1, 3}, {5, 7} } } };
        cout << t[1].t[0].t[1] << t[0].t[1].t[0] << endl;
    
        return 0;
    }
    

    So we have an array of str that holds an array of sct.

    Let's start with the latter. You initialize an array of sct with something like this:

    sct x[2] = { {0, 1}, {2, 3} };
    

    Now for a single instance of str you could go with

    str y = { { {0, 2}, {4, 6} } };
    

    What remains for str t[2] is to arrange two copies of str initializing expressions inside curly brackets:

    str t[2] = { { { {0, 2}, {4, 6} } }, { { {1, 3}, {5, 7} } } };
    

    Edited: On the first reading I misunderstood the question. After the post was updated it became clear that the question is on why it is possible to shed two pair of braces but shedding just one pair results in a syntax error.

    To understand how the parser is interpreting the code you might like to look at the parse tree. You can make gcc dump trees at several stages of the parser with -fdump-tree-... options. Here -fdump-tree-original may be useful.

    To avoid additional confusion let's make sure the elements of the structures have different names:

    struct sct
    {
        int a[2];
    };
    
    struct str
    {
        sct b[2];
    };
    

    Here is the output I got with GCC 7.5 from

    >>>> CODE:
    str t[2] = { { 0, 2, 4, 6 }, { 1, 3, 5, 7 } };
    >>>> tree enabled by -tree-original
    struct str t[2] = {{.b={{.a={0, 2}}, {.a={4, 6}}}}, {.b={{.a={1, 3}}, {.a={5, 7}}}}};
    

    You can see that the compiler adds implicit brackets around the initializing expressions for each structure and around the initializing expressions for each named field.

    Now consider the expression that fails to compile:

    str t[2] = {  { {0, 2},{4, 6} }, { {1, 3},{5, 7} }   };
    

    A the upper level the tree for this expression would be

    /*Step 1: */ struct str t[2] = { {.b={0, 2}, {4, 6} }, {.b={1, 3}, {5, 7} } };
    

    But as b is an array of sct, we try to initialize that with {0,2} getting

    sct b[2] = {0, 2};
    

    This expands to

    struct sct b[2] = {{.a={0, 2} }};
    

    This is valid C++ since the first element of the array is initialized explicitly and the second element is initialized implicitly with zeros.

    With this knowledge we get the following tree

    /*Step 2: */ struct str t[2] = { {.b={{.a={0, 2} }}, {4, 6} }, {.b={{.a={1, 3} }}, {5, 7} } };
    

    Now we are left with the following:

     struct str z = { { { {0,2} }, { {0,0} } }, {4, 6} };
    

    And the compiler rightfully complains:

     error: too many initializers for ‘str’
    

    As a final check consider the following declaration

     struct sxc
     {
         sct b[2];
         int c[2];
     }
    
     struct sxc z = { {0,2} , {4, 6} };
    

    This compiles and result in the following structure:

     { .b = { { .a={0,2} }, { .a={0,0} } }, .c={4, 6} }