int a = {1,2}; Vírgula estranha permitida. Alguma razão em particular?

Talvez eu não seja deste planeta, mas parece-me que o seguinte deve ser um erro de syntax:

int a[] = {1,2,}; //extra comma in the end 

Mas isso não. Fiquei surpreso quando este código compilado no Visual Studio, mas eu aprendi a não confiar no compilador MSVC no que diz respeito às regras C ++, então eu verifiquei o padrão e ele é permitido pelo padrão também. Você pode ver 8.5.1 para as regras gramaticais se você não acredita em mim.

insira a descrição da imagem aqui

Por que isso é permitido? Esta pode ser uma pergunta inútil, mas quero que você entenda por que estou perguntando. Se fosse um sub-case de uma regra gramatical geral, eu entenderia – eles decidiram não tornar a gramática geral mais difícil apenas para desautorizar uma vírgula redundante no final de uma lista de inicializadores. Mas não, a vírgula adicional é explicitamente permitida. Por exemplo, não é permitido ter uma vírgula redundante no final de uma lista de argumentos de chamada de function (quando a function leva ... ), o que é normal .

Então, novamente, existe alguma razão particular para que essa vírgula redundante seja explicitamente permitida?

Isso facilita a geração de código-fonte e também a gravação de código que pode ser facilmente estendido em uma data posterior. Considere o que é necessário para adicionar uma input extra a:

 int a[] = { 1, 2, 3 }; 

… você tem que adicionar a vírgula à linha existente e adicionar uma nova linha. Compare isso com o caso em que os três têm uma vírgula depois, onde você só precisa adicionar uma linha. Da mesma forma, se você quiser remover uma linha, pode fazê-lo sem se preocupar se é a última linha ou não, e pode reordenar as linhas sem mexer nas vírgulas. Basicamente, isso significa que há uma uniformidade na maneira como você trata as linhas.

Agora pense em gerar código. Algo parecido com (pseudo-código):

 output("int a[] = {"); for (int i = 0; i < items.length; i++) { output("%s, ", items[i]); } output("};"); 

Não precisa se preocupar se o item atual que você está escrevendo é o primeiro ou o último. Muito mais simples.

É útil se você fizer algo assim:

 int a[] = { 1, 2, 3, //You can delete this line and it's still valid }; 

Facilidade de uso para o desenvolvedor, eu acho.

 int a[] = { 1, 2, 2, 2, 2, 2, /*line I could comment out easily without having to remove the previous comma*/ } 

Além disso, se por qualquer motivo você tivesse uma ferramenta que gerasse código para você; a ferramenta não precisa se preocupar se é o último item da boot ou não.

Eu sempre achei que fica mais fácil adicionar elementos extras:

 int a[] = { 5, 6, }; 

simplesmente se torna:

 int a[] = { 5, 6, 7, }; 

Numa data posterior.

Tudo o que todo mundo está dizendo sobre a facilidade de adicionar / remover / gerar linhas está correto, mas o local real que essa syntax brilha é ao mesclar os arquivos de origem. Imagine que você tenha esse array:

 int ints[] = { 3, 9 }; 

E suponha que você tenha verificado esse código em um repository.

Então o seu amigo edita, adicionando ao final:

 int ints[] = { 3, 9, 12 }; 

E você simultaneamente edita, adicionando ao começo:

 int ints[] = { 1, 3, 9 }; 

Semanticamente, esses tipos de operações (adicionando ao início, adicionando ao final) devem ser totalmente mesclados com segurança e seu software de version control (esperançosamente git) deve poder ser automaticamente executado. Infelizmente, este não é o caso porque a sua versão não tem vírgula após o 9 e o seu amigo sim. Considerando que, se a versão original tivesse o trailing 9, eles teriam automerged.

Então, minha regra é: use a vírgula à direita se a lista abranger várias linhas, não a use se a lista estiver em uma única linha.

Vírgula à direita, acredito, é permitida por motivos de compatibilidade com versões anteriores. Há muito código existente, principalmente gerado automaticamente, que coloca uma vírgula à direita. Torna mais fácil escrever um loop sem condições especiais no final. por exemplo

 for_each(my_inits.begin(), my_inits.end(), [](const std::string& value) { std::cout < < value << ",\n"; }); 

Não há realmente nenhuma vantagem para o programador.

PS Embora seja mais fácil autogerar o código dessa maneira, na verdade sempre tomei cuidado para não colocar a vírgula à direita, os esforços são mínimos, a legibilidade é aprimorada e isso é mais importante. Você escreve o código uma vez, você o lê muitas vezes.

Uma das razões pelas quais isso é permitido, até onde eu sei, é que deve ser simples gerar código automaticamente; você não precisa de nenhum tratamento especial para o último elemento.

Faz geradores de código que citam matrizes ou enumerações mais fácil.

Imagine:

 std::cout < < "enum Items {\n"; for(Items::iterator i(items.begin()), j(items.end); i != j; ++i) std::cout << *i << ",\n"; std::cout << "};\n"; 

Ou seja, não há necessidade de fazer um tratamento especial do primeiro ou último item para evitar cuspir a vírgula à direita.

Se o gerador de código é escrito em Python, por exemplo, é fácil evitar cuspir a vírgula final usando a function str.join() :

 print("enum Items {") print(",\n".join(items)) print("}") 

A única linguagem em que é – na prática * – não permitida é o Javascript, e causa uma quantidade inumerável de problemas. Por exemplo, se você copiar e colar uma linha do meio da matriz, colá-la no final e esquecer de remover a vírgula, seu site será totalmente quebrado para os visitantes do IE.

* Em teoria, é permitido, mas o Internet Explorer não segue o padrão e trata-o como um erro

O motivo é trivial: facilidade de adicionar / remover linhas.

Imagine o seguinte código:

 int a[] = { 1, 2, //3, // - not needed any more }; 

Agora, você pode facilmente adicionar / remover itens à lista sem ter que adicionar / remover a vírgula final às vezes.

Em contraste com outras respostas, eu realmente não acho que a facilidade de gerar a lista seja uma razão válida: afinal de contas, é trivial para o código em especial a última (ou primeira) linha. Os geradores de código são escritos uma vez e usados ​​muitas vezes.

É mais fácil para máquinas, ou seja, análise e geração de código. Também é mais fácil para os seres humanos, ou seja, modificação, comentando e elegância visual através da consistência.

Assumindo C, você escreveria o seguinte?

 #include  #include  int main(void) { puts("Line 1"); puts("Line 2"); puts("Line 3"); return EXIT_SUCCESS } 

Não. Não apenas porque a declaração final é um erro, mas também porque é inconsistente. Então, por que fazer o mesmo com as collections? Mesmo em idiomas que permitem omitir os últimos pontos e vírgulas, a comunidade geralmente não gosta disso. A comunidade Perl, por exemplo, não parece gostar de omitir ponto-e-vírgula, mas não de um-liner. Eles também aplicam isso a vírgulas.

Não omita vírgulas em collections multilinhas pela mesma razão que você não omite ponto-e-vírgulas para blocos de código de várias linhas. Quero dizer, você não faria isso mesmo que a linguagem permitisse, certo? Certo?

Estou surpreso depois de todo esse tempo que ninguém citou o Annotated C ++ Reference Manual ( ARM ), ele diz o seguinte sobre [dcl.init] com ênfase minha:

Há claramente muitas notações para inicializações, mas cada uma parece servir bem a um estilo particular de uso. A notação = {initializer_list, opt} foi herdada de C e serve bem para a boot de estruturas de dados e matrizes. […]

embora a gramática tenha evoluído desde que a ARM foi escrita, a origem permanece.

e podemos ir ao raciocínio C99 para ver por que isso foi permitido em C e diz:

K & R permite uma vírgula à direita em um inicializador no final de uma lista de inicializadores. O padrão reteve essa syntax, pois fornece flexibilidade para adicionar ou excluir membros de uma lista de inicializadores e simplifica a geração de máquinas dessas listas.

Permite que cada linha siga o mesmo formulário. Em primeiro lugar, isso torna mais fácil adicionar novas linhas e ter um sistema de version control rastreie a alteração de maneira significativa e também permite analisar o código com mais facilidade. Não consigo pensar em uma razão técnica.

Isso é permitido para proteger contra erros causados ​​por elementos em movimento em uma longa lista.

Por exemplo, vamos supor que temos um código parecido com isso.

 #include  #include  #include  #define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array)) int main() { std::string messages[] = { "Stack Overflow", "Super User", "Server Fault" }; size_t i; for (i = 0; i < ARRAY_SIZE(messages); i++) { std::cout << messages[i] << std::endl; } } 

E é ótimo, pois mostra a trilogia original dos sites do Stack Exchange.

 Stack Overflow Super User Server Fault 

Mas há um problema com isso. Você vê, o rodapé deste site mostra falha do servidor antes do Superusuário. Melhor consertar isso antes que alguém perceba.

 #include  #include  #include  #define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array)) int main() { std::string messages[] = { "Stack Overflow", "Server Fault" "Super User", }; size_t i; for (i = 0; i < ARRAY_SIZE(messages); i++) { std::cout << messages[i] << std::endl; } } 

Afinal de contas, as linhas móveis em volta não poderiam ser tão difíceis, poderia ser?

 Stack Overflow Server FaultSuper User 

Eu sei, não existe um site chamado "Server FaultSuper User", mas nosso compilador afirma que existe. Agora, o problema é que C tem um recurso de concatenação de cadeias de caracteres, que permite escrever duas cadeias duplas entre aspas e concatená-las usando nada (problema semelhante também pode acontecer com números inteiros, como - sign tem vários significados).

Agora, e se a matriz original tivesse uma vírgula inútil no final? Bem, as linhas seriam movidas, mas esse bug não teria acontecido. É fácil perder algo tão pequeno quanto uma vírgula. Se você se lembrar de colocar uma vírgula após cada elemento da matriz, esse erro não poderá acontecer. Você não gostaria de perder quatro horas depurando algo, até encontrar a vírgula como causa de seus problemas .

Eu vejo um caso de uso que não foi mencionado em outras respostas, nossos macros favoritos:

 int a [] = { #ifdef A 1, //this can be last if B and C is undefined #endif #ifdef B 2, #endif #ifdef C 3, #endif }; 

Adicionar macros para manipular por último seria uma grande dor. Com essa pequena mudança na syntax, isso é trivial de gerenciar. E isso é mais importante do que o código gerado por máquina, porque geralmente é muito mais fácil fazê-lo em Turing do que um pré-processador muito limitado.

Como muitas outras coisas, a vírgula final em um inicializador de matriz é uma das coisas que o C ++ herdou de C (e terá que suportar para sempre). Uma visão totalmente diferente das aqui apresentadas é mencionada no livro “Deep C secrets” .

Depois de um exemplo com mais de um “paradoxo por vírgula”:

 char *available_resources[] = { "color monitor" , "big disk" , "Cray" /* whoa! no comma! */ "on-line drawing routines", "mouse" , "keyboard" , "power cables" , /* and what's this extra comma? */ }; 

nós lemos :

… aquela vírgula à direita após o inicializador final não é um erro de digitação, mas um pontinho na syntax herdada do C aborígene . Sua presença ou ausência é permitida, mas não tem significado . A justificativa alegada na lógica ANSI C é que facilita a geração automatizada de C. A alegação seria mais credível se as vírgulas à direita fossem permitidas em todas as listas com vírgulas separadas , como em declarações enum ou vários declaradores de variables ​​em uma única declaração. Eles não são.

… para mim isso faz mais sentido

Além da facilidade de geração e edição de código, se você quiser implementar um analisador, esse tipo de gramática é mais simples e fácil de implementar. C # segue esta regra em vários lugares que há uma lista de itens separados por vírgula, como itens em uma definição enum .

Isso facilita a geração de código, pois você só precisa adicionar uma linha e não precisa tratar a adição da última input como se fosse um caso especial. Isso é especialmente verdadeiro ao usar macros para gerar código. Há um esforço para tentar eliminar a necessidade de macros da linguagem, mas muito da linguagem evoluiu de mãos dadas com as macros disponíveis. A vírgula extra permite que macros como as seguintes sejam definidas e usadas:

 #define LIST_BEGIN int a[] = { #define LIST_ENTRY(x) x, #define LIST_END }; 

Uso:

 LIST_BEGIN LIST_ENTRY(1) LIST_ENTRY(2) LIST_END 

Esse é um exemplo muito simplificado, mas muitas vezes esse padrão é usado por macros para definir itens como tabelas e mapas de expedição, mensagem, evento ou tradução. Se uma vírgula não fosse permitida no final, precisaríamos de um especial:

 #define LIST_LAST_ENTRY(x) x 

e isso seria muito estranho de usar.

Se você usar uma matriz sem comprimento especificado, o VC ++ 6.0 pode identificar automaticamente seu tamanho, portanto, se você usar “int a [] = {1,2,};” o comprimento de a é 3, mas o último hasn ‘ t sido inicializado, você pode usar “cout <