Por que não preciso de três níveis de chaves para inicializar três níveis de matrizes?

8

Me deparei com este exemplo

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

Isso compila e funciona bem. Dá a saída34

Eu esperava que a sintaxe para inicialização fosse:

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

Ao invés de

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

Mas isso deu:

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

Alguém pode explicar o porquê?

Esta é uma figura para ilustrar a maneira como vejo isso: insira a descrição da imagem aqui

Como devo pensar quando se trata de inicializar estruturas aninhadas?

Cătălina Sîrbu
fonte
3
Ao fazer perguntas que envolvam erros de construção, sempre inclua os erros reais que você recebe, copiados e colados como texto na íntegra e na íntegra.
Algum programador

Respostas:

7

Parece um erro de digitação simples, mas a situação é complexa o suficiente para lidar com isso passo a passo.

Primeiro, deixe-me mostrar a solução 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;
}

Portanto, temos uma variedade de str que contém uma matriz de sct.

Vamos começar com o último. Você inicializa uma matriz desct algo como isto:

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

Agora, para uma única instância de str você poderia ir com

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

O que resta str t[2]é organizar duas cópias das strexpressões de inicialização entre colchetes:

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

Editado: Na primeira leitura, eu entendi mal a pergunta. Após a atualização da postagem, ficou claro que a pergunta é por que é possível lançar dois pares de chaves, mas eliminar apenas um par resulta em um erro de sintaxe.

Para entender como o analisador está interpretando o código, você pode olhar para a árvore de análise. Você pode fazer o dump de árvores do gcc em vários estágios do analisador com-fdump-tree-... opções. Aqui -fdump-tree-originalpode ser útil.

Para evitar confusão adicional, verifique se os elementos das estruturas têm nomes diferentes:

struct sct
{
    int a[2];
};

struct str
{
    sct b[2];
};

Aqui está a saída que obtive com o 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}}}}};

Você pode ver que o compilador adiciona colchetes implícitos em torno das expressões de inicialização para cada estrutura e em torno das expressões de inicialização para cada campo nomeado.

Agora considere a expressão que falha ao compilar:

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

No nível superior, a árvore para esta expressão seria

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

Mas como b é uma matriz de sct, tentamos inicializar isso com a {0,2}obtenção de

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

Isso se expande para

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

Isso é válido em C ++, pois o primeiro elemento da matriz é inicializado explicitamente e o segundo elemento é inicializado implicitamente com zeros.

Com esse conhecimento, obtemos a seguinte árvore

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

Agora ficamos com o seguinte:

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

E o compilador reclama com razão:

 error: too many initializers for str

Como verificação final, considere a seguinte declaração

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

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

Isso compila e resulta na seguinte estrutura:

 { .b = { { .a={0,2} }, { .a={0,0} } }, .c={4, 6} }
Dmitri Chubarov
fonte
Ok, mas como é que você pode simplesmente enumerar os valores separados por vírgula como str t[2] = { {0, 2, 4, 6}, {1, 3, 5, 7} }. Vejo que o compilador as assume em ordem. A partir da última linha de sua resposta, como você modificaria para chegar a esta { {0, 2, 4, 6}, {1, 3, 5, 7} }?
Cătălina Sîrbu
Quero dizer, qual seria a razão pela qual você pode se livrar de alguns colchetes?
Cătălina Sîrbu
1
Você pode entender mal a intenção da pergunta @Dimitri. Esse código compila e executa bem. Eu acho que ela está perguntando "não precisa de outro conjunto de chaves ( { })?"
northerner
1
@ CătălinaSîrbu Na verdade, eu sei que perdi a intenção do seu post original, obrigado pelo esclarecimento. Atualizei a resposta com uma tentativa de reconstruir a lógica do compilador.
Dmitri Chubarov
1
@ CătălinaSîrbu A notação de ponto é usada pelo compilador para anotar a árvore de análise. Para facilitar a leitura, renomei o nome sct.te str.tpara sct.ae str.brespectivamente. É daí .ae de onde .bveio.
Dmitri Chubarov