Por que o estreitamento da conversão usada com o inicializador delimitado por chave de chaves causa um erro?

Eu aprendi sobre o inicializador delimitado por chaves em linguagem C ++ Programming Language, 4th ed. > Capítulo 2: Um Tour de C ++: O Básico.

Estou citando o livro abaixo.

O formulário = é tradicional e remonta a C, mas, em caso de dúvida, use o formulário geral {} -list (§6.3.5.2). Se nada mais, você economiza de conversões que perdem informações (estreitando conversões; §10.5):

int i1 = 7.2; // i1 becomes 7 int i2 {7.2}; // error : floating-point to integer conversion int i3 = {7.2}; // error : floating-point to integer conversion (the = is redundant) 

No entanto, não consigo reproduzir esses resultados.

Eu tenho o seguinte código.

 #include  int main() { int i1 = 7.2; int i2 {7.2}; int i3 = {7.2}; std::cout << i1 << "\n"; std::cout << i2 << "\n"; std::cout << i3 << "\n"; } 

Quando eu compilo e executo, não recebo nenhum erro. Eu recebo um aviso sobre std=c++11 mas nenhum erro.

 $ g++ init.cpp init.cpp: In function 'int main()': init.cpp:6:12: warning: extended initializer lists only available with -std=c++11 or -std=gnu++11 int i2 {7.2}; ^ $ ./a.out 7 7 7 

Além disso, o aviso é apenas para a segunda atribuição, mas não há aviso para a terceira atribuição. Isto parece indicar que o = não é realmente redundante como mencionado no livro. Se = fossem redundantes, tanto a segunda como a terceira atribuições teriam produzido avisos ou ambos não teriam produzido avisos. Então eu compilo-os com o -std=c++11 .

 $ g++ -std=c++11 init.cpp init.cpp: In function 'int main()': init.cpp:6:16: warning: narrowing conversion of '7.2000000000000002e+0' from 'double' to 'int' inside { } [-Wnarrowing] int i2 {7.2}; ^ init.cpp:7:18: warning: narrowing conversion of '7.2000000000000002e+0' from 'double' to 'int' inside { } [-Wnarrowing] int i3 = {7.2}; ^ $ ./a.out 7 7 7 

Ainda sem erro. Apenas avisos. Embora neste caso as segundas e terceiras atribuições se comportem de maneira idêntica em relação à geração de avisos.

Portanto, minha pergunta é: Embora o livro mencione que as segundas e terceiras atribuições são erros, por que esse código não compila?

Isso é mal formado e deve haver diagnóstico, no entanto, pode ser um aviso ( que você recebeu ) ou um erro. O gcc fez disso um aviso para várias versões devido ao problema de portabilidade do C ++ 03 :

A norma exige apenas que “uma implementação em conformidade deve emitir pelo menos uma mensagem de diagnóstico”, de modo que é permitido compilar o programa com uma advertência. Como Andrew disse, -Werror = estreitamento permite que você faça um erro, se quiser.

G ++ 4.6 deu um erro, mas foi alterado para um aviso intencionalmente para 4.7 porque muitas pessoas (inclusive eu) descobriram que estreitar conversões era um dos problemas mais comumente encontrados ao tentar compilar grandes bases de código C ++ 03 como C ++ 11. Código anteriormente bem formado, como char c [] = {i, 0}; (onde eu só vou estar sempre dentro do intervalo de char) causou erros e teve que ser alterado para char c [] = {(char) i, 0}

mas agora as versões recentes do gcc e do clang tornam isso um erro, veja-o ao vivo para gcc .

Para referência, o rascunho da seção padrão C ++ 11 8.5.4 [dcl.init.list] diz:

Caso contrário, se a lista de inicializadores tiver um único elemento, o object ou referência será inicializado a partir desse elemento; se uma conversão de restrição (veja abaixo) for necessária para converter o elemento para T, o programa será mal formado. [Exemplo:

 int x1 {2}; // OK int x2 {2.0}; // error: narrowing 

– por exemplo

e:

Uma conversão de restrição é uma conversão implícita

  • de um tipo de ponto flutuante para um tipo inteiro, ou

[…]

[Observação: conforme indicado acima, essas conversões não são permitidas no nível superior nas inicializações de lista. – nota final] [Exemplo:

[…]

 int ii = {2.0}; // error: narrows 

[…]

Portanto, um ponto flutuante para a conversão de números inteiros é uma conversão restrita e é malformada.

e seção 1.4 Conformidade com a implementação [intro.compliance] diz:

Embora esta Norma exponha apenas os requisitos das implementações de C ++, esses requisitos geralmente são mais fáceis de entender se forem expressos como requisitos em programas, partes de programas ou execução de programas. Tais requisitos têm o seguinte significado:

[…]

  • Se um programa contiver uma violação de qualquer regra diagnosticável ou uma ocorrência de uma construção descrita nesta Norma como “suportada condicionalmente” quando a implementação não suportar essa construção, uma implementação em conformidade deverá emitir pelo menos uma mensagem de diagnóstico.

[…]

Diz-nos que apenas um diagnóstico é necessário.

A linguagem C ++ não distingue “avisos” de “erros”. C ++ só tem mensagens de diagnóstico . Os avisos que você recebeu são mensagens de diagnóstico. A especificação da linguagem não requer compiladores para interromper a compilation quando eles encontram códigos errôneos (também conhecidos como mal formados). Todos os compiladores têm que fazer é emitir uma mensagem de diagnóstico e, em seguida, eles podem continuar a compilar, se assim o desejarem.

Isso significa que, em geral, é sua responsabilidade informar aos avisos que são “apenas avisos” de avisos que realmente indicam erros genuínos, especialmente com compiladores permissivos como o GCC.

Isso também significa que o comportamento real da vida real é uma questão de configuração do compilador. Peça ao seu compilador para ser mais restritivo a esse respeito, se possível. No GCC, você pode tentar a opção -pedantic-errors para essa finalidade.

PS Em minhas experiências com o GCC, -std=c++11 é suficiente para gerar erros para o seu código. Se você está recebendo avisos, pode ser uma questão de versão do compilador.