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?


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.


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.


12

Você poderia

A a(());

usar

A a=A();

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.