Minha tentativa de inicialização de valor é interpretada como uma declaração de função e por que A não (()); resolvê-lo?

158

Entre as muitas coisas que o Stack Overflow me ensinou é o que é conhecido como a "análise mais irritante", que é demonstrada classicamente com uma linha como

A a(B()); //declares a function

Enquanto isso, para a maioria, parece intuitivamente ser a declaração de um objeto ado tipo A, pegando um Bobjeto temporário como parâmetro construtor, na verdade é uma declaração de uma função aretornando um A, levando um ponteiro para uma função que retorna Be não aceita parâmetros . Da mesma forma a linha

A a(); //declares a function

também se enquadra na mesma categoria, pois, em vez de um objeto, declara uma função. Agora, no primeiro caso, a solução usual para esse problema é adicionar um conjunto extra de colchetes / parênteses ao redor do B(), pois o compilador o interpretará como a declaração de um objeto

A a((B())); //declares an object

No entanto, no segundo caso, fazer o mesmo leva a um erro de compilação

A a(()); //compile error

Minha pergunta é por que? Sim, eu estou muito ciente de que a 'solução alternativa' correta é alterá-la para A a;, mas estou curioso para saber o que o extra ()faz para o compilador no primeiro exemplo, que depois não funciona ao aplicá-lo novamente. o segundo exemplo. A A a((B()));solução alternativa é uma exceção específica gravada no padrão?

GRB
fonte
20
(B())é apenas uma expressão C ++, nada mais. Não é nenhum tipo de exceção. A única diferença que faz é que não há como ele ser analisado como um tipo e, portanto, não é.
Pavel Minaev 15/09/09
12
Note-se também que o segundo caso, nãoA a(); é da mesma categoria. Para o compilador , nunca há uma maneira diferente de analisá-lo: um inicializador nesse local nunca consiste em parênteses vazios; portanto, essa é sempre uma declaração de função.
Johannes Schaub - litb
11
o ponto excelente de litb é sutil, mas importante, e vale a pena enfatizar - a razão pela qual a ambiguidade existe nesta declaração 'A a (B ())' está na análise de 'B ()' -> pode ser uma expressão e uma declaração e o compilador deve 'escolher' decl over sobre expr - assim, se B () for um decl, então 'a' poderá ser apenas um func decl (não uma variável decl). Se '()' tivesse permissão para ser um inicializador 'A a ()' seria ambíguo - mas não expr vs decl, mas var decl vs func decl - não há regra para preferir um declínio a outro - e assim '() 'simplesmente não é permitido como inicializador aqui - e a ambiguidade não aumenta.
Faisal Vali
6
A a();não é um exemplo da análise mais irritante . É simplesmente uma declaração de função, assim como é em C.
Pete Becker
2
"a 'solução alternativa' correta é alterá-lo para A a;" está errado. Isso não lhe dará a inicialização de um tipo de POD. Para obter a inicialização, escreva A a{};.
Saúde e hth. #

Respostas:

70

Não há resposta esclarecida, é apenas porque ela não é definida como sintaxe válida pela linguagem C ++ ... Por isso é assim, por definição da linguagem.

Se você tiver uma expressão dentro dela, ela é válida. Por exemplo:

 ((0));//compiles

Ainda mais simples: porque (x)é uma expressão C ++ válida, enquanto ()não é.

Para aprender mais sobre como as línguas são definidas e como os compiladores funcionam, você deve aprender sobre a teoria formal da linguagem ou, mais especificamente, Gramáticas Livres de Contexto (CFG) e material relacionado, como máquinas de estados finitos. Se você estiver interessado nisso, embora as páginas da Wikipedia não sejam suficientes, você terá que adquirir um livro.

Brian R. Bondy
fonte
45
Ainda mais simples: porque (x)é uma expressão C ++ válida, enquanto ()não é.
Pavel Minaev 15/09/2009
Eu aceitei esta resposta, em comentário Além de Pavel à minha pergunta inicial me ajudou muito
GRB
29

Declaradores de função C

Primeiro de tudo, há C. Em C, A a()há declaração de função. Por exemplo, putchartem a seguinte declaração. Normalmente, essas declarações são armazenadas nos arquivos de cabeçalho; no entanto, nada impede que você as escreva manualmente, se você souber como é a declaração da função. Os nomes dos argumentos são opcionais nas declarações, então eu o omiti neste exemplo.

int putchar(int);

Isso permite que você escreva o código assim.

int puts(const char *);
int main() {
    puts("Hello, world!");
}

C também permite definir funções que aceitam funções como argumentos, com uma sintaxe legível que se parece com uma chamada de função (bem, é legível, desde que você não retorne um ponteiro para a função).

#include <stdio.h>

int eighty_four() {
    return 84;
}

int output_result(int callback()) {
    printf("Returned: %d\n", callback());
    return 0;
}

int main() {
    return output_result(eighty_four);
}

Como eu mencionei, C permite omitir nomes de argumentos nos arquivos de cabeçalho, portanto, output_resultficaria assim no arquivo de cabeçalho.

int output_result(int());

Um argumento no construtor

Você não reconhece esse? Bem, deixe-me lembrá-lo.

A a(B());

Sim, é exatamente a mesma declaração de função. Aé int, aé output_resulte Bé int.

Você pode perceber facilmente um conflito de C com novos recursos do C ++. Para ser exato, construtores sendo o nome da classe e parênteses, e sintaxe de declaração alternativa com em ()vez de =. Por design, o C ++ tenta ser compatível com o código C e, portanto, precisa lidar com esse caso - mesmo que praticamente ninguém se importe. Portanto, os recursos antigos do C têm prioridade sobre os novos recursos do C ++. A gramática das declarações tenta corresponder o nome como função, antes de reverter para a nova sintaxe ()se ela falhar.

Se um desses recursos não existisse ou tivesse uma sintaxe diferente (como {}no C ++ 11), esse problema nunca teria acontecido para a sintaxe com um argumento.

Agora você pode perguntar por que A a((B()))funciona. Bem, vamos declarar output_resultcom parênteses inúteis.

int output_result((int()));

Isso não vai funcionar. A gramática requer que a variável não esteja entre parênteses.

<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token

No entanto, C ++ espera expressão padrão aqui. Em C ++, você pode escrever o seguinte código.

int value = int();

E o seguinte código.

int value = ((((int()))));

O C ++ espera que a expressão dentro dos parênteses seja ... bem ... expressão, em oposição ao tipo C espera. Parênteses não significam nada aqui. No entanto, inserindo parênteses inúteis, a declaração da função C não é correspondida e a nova sintaxe pode ser correspondida corretamente (o que simplesmente espera uma expressão, como 2 + 2).

Mais argumentos no construtor

Certamente um argumento é bom, mas e dois? Não é que os construtores possam ter apenas um argumento. Uma das classes internas que recebe dois argumentos éstd::string

std::string hundred_dots(100, '.');

Está tudo bem (tecnicamente, teria a análise mais irritante se fosse escrita como std::string wat(int(), char()), mas sejamos honestos - quem escreveria isso? Mas vamos assumir que esse código tenha um problema irritante. Você supõe que deve colocar tudo entre parênteses.

std::string hundred_dots((100, '.'));

Não é bem assim.

<stdin>:2:36: error: invalid conversion from char to const char*’ [-fpermissive]
In file included from /usr/include/c++/4.8/string:53:0,
                 from <stdin>:1:
/usr/include/c++/4.8/bits/basic_string.tcc:212:5: error:   initializing argument 1 of std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
     basic_string<_CharT, _Traits, _Alloc>::
     ^

Não sei por que o g ++ tenta converter charpara const char *. De qualquer maneira, o construtor foi chamado com apenas um valor do tipo char. Não há sobrecarga que tenha um argumento do tipo char, portanto, o compilador está confuso. Você pode perguntar - por que o argumento é do tipo char?

(100, '.')

Sim, ,aqui está um operador de vírgula. O operador de vírgula recebe dois argumentos e fornece o argumento do lado direito. Não é realmente útil, mas é algo a ser conhecido pela minha explicação.

Em vez disso, para resolver a análise mais irritante, é necessário o seguinte código.

std::string hundred_dots((100), ('.'));

Os argumentos estão entre parênteses, não a expressão inteira. De fato, apenas uma das expressões precisa estar entre parênteses, pois é suficiente interromper um pouco a gramática C para usar o recurso C ++. As coisas nos levam ao ponto de zero argumentos.

Zero argumento no construtor

Você pode ter notado a eighty_fourfunção na minha explicação.

int eighty_four();

Sim, isso também é afetado pela análise mais irritante. É uma definição válida e provavelmente você já viu se criou arquivos de cabeçalho (e deveria). Adicionar parênteses não o corrige.

int eighty_four(());

Por que? Bem, ()não é uma expressão. No C ++, você deve colocar uma expressão entre parênteses. Você não pode escrever auto value = ()em C ++, porque ()isso não significa nada (e mesmo que isso significasse, como tupla vazia (consulte Python), seria um argumento, não zero). Na prática, isso significa que você não pode usar a sintaxe abreviada sem usar a {}sintaxe do C ++ 11 , pois não há expressões entre parênteses e a gramática C para declarações de função sempre será aplicada.

Konrad Borowski
fonte
12

Você poderia

A a(());

usar

A a=A();
user265149
fonte
32
A 'solução alternativa melhor' não é equivalente. int a = int();inicializa acom 0, int a;deixa anão inicializado. Uma solução correta é usar A a = {};para agregados, A a;quando a inicialização padrão faz o que você deseja e A a = A();em todos os outros casos - ou apenas usa de forma A a = A();consistente. No C ++ 11, basta usarA a {};
Richard Smith
6

O parênteses mais interno no seu exemplo seria uma expressão e, em C ++, a gramática define um expressionpara ser um assignment-expressionou outro expressionseguido por vírgula e outro assignment-expression(Apêndice A.4 - Resumo / expressões gramaticais).

A gramática define ainda assignment-expressioncomo um dos vários outros tipos de expressão, nenhum dos quais pode ser nada (ou apenas espaço em branco).

Portanto, a razão pela qual você não pode ter A a(())é simplesmente porque a gramática não permite. No entanto, não sei responder por que as pessoas que criaram C ++ não permitiram esse uso específico de parênteses vazias como algum tipo de caso especial - eu acho que eles preferem não colocar em um caso tão especial se houver uma alternativa razoável.

Michael Burr
fonte