¿Por qué no necesito 3 niveles de llaves para inicializar 3 niveles de matrices?

8

Me encontré con este ejemplo

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;
}

Esto compila y funciona bien. Da la salida34

Esperaba que la sintaxis para la inicialización fuera:

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

En vez de

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

Pero esto dio:

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

¿Alguien puede explicar por qué?

Esta es una imagen para ilustrar la forma en que veo esto: ingrese la descripción de la imagen aquí

¿Cómo debo pensar cuando se trata de inicializar estructuras anidadas?

Cătălina Sîrbu
fuente
3
Cuando haga preguntas relacionadas con errores de compilación, incluya siempre los errores reales que obtiene, copiados y pegados como texto completo y completo.
Algún tipo programador

Respuestas:

7

Esto parece un error tipográfico simple, pero la situación es lo suficientemente compleja como para abordarlo paso a paso.

Primero déjame mostrarte la solución que parece funcionar:

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;
}

Entonces tenemos una matriz de strque contiene una matriz de sct.

Comencemos con el último. Inicializa una matriz de sctcon algo como esto:

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

Ahora, para una sola instancia de strusted podría ir con

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

Lo que queda str t[2]es organizar dos copias de las strexpresiones de inicialización entre llaves:

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

Editado: en la primera lectura no entendí la pregunta. Después de que se actualizó la publicación, quedó claro que la pregunta es por qué es posible arrojar dos pares de llaves, pero arrojar solo un par da como resultado un error de sintaxis.

Para comprender cómo interpreta el analizador el código, puede que desee mirar el árbol de análisis. Puede hacer gcc dump trees en varias etapas del analizador con -fdump-tree-...opciones. Aquí -fdump-tree-originalpuede ser útil.

Para evitar confusiones adicionales, asegurémonos de que los elementos de las estructuras tengan nombres diferentes:

struct sct
{
    int a[2];
};

struct str
{
    sct b[2];
};

Aquí está la salida que obtuve con GCC 7.5 de

>>>> 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}}}}};

Puede ver que el compilador agrega corchetes implícitos alrededor de las expresiones de inicialización para cada estructura y alrededor de las expresiones de inicialización para cada campo con nombre.

Ahora considere la expresión que no se compila:

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

A el nivel superior, el árbol para esta expresión sería

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

Pero a medida que b es un vector de sct, tratamos de que inicializar con {0,2}conseguir

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

Esto se expande a

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

Esto es válido en C ++ ya que el primer elemento de la matriz se inicializa explícitamente y el segundo elemento se inicializa implícitamente con ceros.

Con este conocimiento obtenemos el siguiente árbol

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

Ahora nos queda lo siguiente:

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

Y el compilador se queja legítimamente:

 error: too many initializers for str

Como verificación final, considere la siguiente declaración

 struct sxc
 {
     sct b[2];
     int c[2];
 }

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

Esto compila y da como resultado la siguiente estructura:

 { .b = { { .a={0,2} }, { .a={0,0} } }, .c={4, 6} }
Dmitri Chubarov
fuente
De acuerdo, pero ¿cómo es que se le permite simplemente enumerar separados por comas los valores como str t[2] = { {0, 2, 4, 6}, {1, 3, 5, 7} }? Veo que el compilador los asume en orden. Desde la última línea de tu respuesta, ¿cómo modificarías para llegar a esta { {0, 2, 4, 6}, {1, 3, 5, 7} }?
Cătălina Sîrbu
Quiero decir, ¿cuál sería la razón por la que puedes deshacerte de algunos corchetes?
Cătălina Sîrbu
1
Puede malinterpretar la intención de la pregunta @Dimitri. Este código se compila y funciona bien. Creo que está preguntando "¿no necesita otro conjunto de llaves ( { })?"
norteño
1
@ CătălinaSîrbu De hecho, me doy cuenta de que perdí la intención de tu publicación original, gracias por la aclaración. He actualizado la respuesta con un intento de reconstruir la lógica del compilador.
Dmitri Chubarov
1
@ CătălinaSîrbu El compilador utiliza la notación de puntos para anotar el árbol de análisis. Con el fin de facilitar las cosas para leer Retitulé sct.ty str.tde sct.ay str.b, respectivamente. Ahí es donde .ay .bvienen.
Dmitri Chubarov