Mi intento de inicialización de valor se interpreta como una declaración de función, y por qué no A a (()); ¿resuélvelo?

158

Entre las muchas cosas que Stack Overflow me ha enseñado está lo que se conoce como el "análisis más irritante", que se demuestra clásicamente con una línea como

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

Si bien esto, para la mayoría, intuitivamente parece ser la declaración de un objeto ade tipo A, tomando un Bobjeto temporal como un parámetro constructor, en realidad es una declaración de una función que adevuelve un A, tomando un puntero a una función que regresa By no toma parámetros . Del mismo modo la línea

A a(); //declares a function

También cae dentro de la misma categoría, ya que en lugar de un objeto, declara una función. Ahora, en el primer caso, la solución habitual para este problema es agregar un conjunto adicional de corchetes / paréntesis alrededor B(), ya que el compilador lo interpretará como la declaración de un objeto

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

Sin embargo, en el segundo caso, hacer lo mismo conduce a un error de compilación

A a(()); //compile error

Mi pregunta es, ¿por qué? Sí, soy muy consciente de que la "solución" correcta es cambiarlo A a;, pero tengo curiosidad por saber qué hace el extra ()para el compilador en el primer ejemplo, que luego no funciona al volver a aplicarlo El segundo ejemplo. ¿La A a((B()));solución es una excepción específica escrita en el estándar?

GRB
fuente
20
(B())es solo una expresión de C ++, nada más. No es ningún tipo de excepción. La única diferencia que hace es que no hay forma de que pueda analizarse como un tipo, por lo que no lo es.
Pavel Minaev
12
También debe tenerse en cuenta que el segundo caso, noA a(); es de la misma categoría. Para el compilador , nunca hay una forma diferente de analizarlo: un inicializador en ese lugar nunca consta de paréntesis vacíos, por lo que esta siempre es una declaración de función.
Johannes Schaub - litb
11
El excelente punto de litb es sutil pero importante y vale la pena enfatizar: la razón por la que existe ambigüedad en esta declaración 'A a (B ())' es el análisis de 'B ()' -> puede ser una expresión y una declaración y el compilador debe 'elegir' declinar sobre expr, por lo que si B () es un declive, entonces 'a' solo puede ser un declive funcional (no un declive variable). Si se permitiera que '()' fuera un inicializador, 'A a ()' sería ambiguo, pero no expr vs decl, sino var decl vs func decl: no hay una regla para preferir un declive sobre otro, y entonces '() 'simplemente no está permitido como inicializador aquí, y la ambigüedad no aumenta.
Faisal Vali
66
A a();No es un ejemplo del análisis más irritante . Es simplemente una declaración de función, al igual que en C.
Pete Becker
2
"la" solución "correcta es cambiarlo a A a;" está mal ". Eso no te dará la inicialización de un tipo de POD. Para obtener la inicialización escriba A a{};.
Saludos y hth. - Alf

Respuestas:

70

No hay una respuesta ilustrada, es solo porque no está definida como sintaxis válida por el lenguaje C ++ ... Por lo tanto, es así, por definición del lenguaje.

Si tiene una expresión dentro, entonces es válida. Por ejemplo:

 ((0));//compiles

Aún más simple: porque (x)es una expresión válida de C ++, mientras ()que no lo es.

Para obtener más información sobre cómo se definen los idiomas y cómo funcionan los compiladores, debe conocer la teoría del lenguaje formal o, más específicamente, las gramáticas libres de contexto (CFG) y el material relacionado, como las máquinas de estados finitos. Si está interesado en eso, aunque las páginas de Wikipedia no serán suficientes, tendrá que obtener un libro.

Brian R. Bondy
fuente
45
Aún más simple: porque (x)es una expresión válida de C ++, mientras ()que no lo es.
Pavel Minaev
He aceptado esta respuesta, además, el comentario de Pavel a mi pregunta inicial me ayudó mucho
GRB
29

C declaradores de funciones

En primer lugar, hay C. En C, A a()es la declaración de función. Por ejemplo, putchartiene la siguiente declaración. Normalmente, tales declaraciones se almacenan en archivos de encabezado, sin embargo, nada le impide escribirlas manualmente, si sabe cómo se ve la declaración de función. Los nombres de los argumentos son opcionales en las declaraciones, por lo que lo omití en este ejemplo.

int putchar(int);

Esto le permite escribir el código de esta manera.

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

C también le permite definir funciones que toman funciones como argumentos, con una buena sintaxis legible que parece una llamada a función (bueno, es legible, siempre que no devuelva un puntero a la función).

#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 mencioné, C permite omitir nombres de argumentos en los archivos de encabezado, por lo tanto, output_resultse vería así en el archivo de encabezado.

int output_result(int());

Un argumento en constructor

¿No lo reconoces? Bueno, déjame recordarte.

A a(B());

Sí, es exactamente la misma declaración de función. Aes int, aes output_resulty Bes int.

Puede notar fácilmente un conflicto de C con las nuevas características de C ++. Para ser exactos, los constructores son nombre de clase y paréntesis, y sintaxis de declaración alternativa con en ()lugar de =. Por diseño, C ++ intenta ser compatible con el código C y, por lo tanto, tiene que lidiar con este caso, incluso si prácticamente a nadie le importa. Por lo tanto, las características antiguas de C tienen prioridad sobre las nuevas características de C ++. La gramática de las declaraciones intenta hacer coincidir el nombre como función, antes de volver a la nueva sintaxis con ()si falla.

Si una de esas características no existiera, o tuviera una sintaxis diferente (como {}en C ++ 11), este problema nunca hubiera ocurrido para la sintaxis con un argumento.

Ahora puede preguntar por qué A a((B()))funciona. Bueno, declaremos output_resultcon paréntesis inútiles.

int output_result((int()));

No va a funcionar La gramática requiere que la variable no esté entre paréntesis.

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

Sin embargo, C ++ espera una expresión estándar aquí. En C ++, puede escribir el siguiente código.

int value = int();

Y el siguiente código.

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

C ++ espera que la expresión dentro de paréntesis sea ... bueno ... expresión, a diferencia del tipo C que espera. Los paréntesis no significan nada aquí. Sin embargo, al insertar paréntesis inútiles, la declaración de la función C no coincide, y la nueva sintaxis puede coincidir correctamente (lo que simplemente espera una expresión, como 2 + 2).

Más argumentos en constructor

Seguramente un argumento es bueno, pero ¿y dos? No es que los constructores puedan tener un solo argumento. Una de las clases integradas que toma dos argumentos esstd::string

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

Todo esto está muy bien (técnicamente, sería más irritante si se escribiera como std::string wat(int(), char()), pero seamos honestos, ¿quién escribiría eso? Pero supongamos que este código tiene un problema molesto. Asumirías que tienes que poner todo entre paréntesis.

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

No del todo.

<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>::
     ^

No estoy seguro de por qué g ++ intentos para convertir chara const char *. De cualquier manera, se llamó al constructor con solo un valor de tipo char. No hay sobrecarga que tenga un argumento de tipo char, por lo tanto, el compilador está confundido. Usted puede preguntar: ¿por qué el argumento es de tipo char?

(100, '.')

Sí, ,aquí hay un operador de coma. El operador de coma toma dos argumentos y da el argumento del lado derecho. No es realmente útil, pero es algo que se debe conocer por mi explicación.

En cambio, para resolver el análisis más irritante, se necesita el siguiente código.

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

Los argumentos están entre paréntesis, no toda la expresión. De hecho, solo una de las expresiones debe estar entre paréntesis, ya que es suficiente para romper ligeramente la gramática de C para usar la función C ++. Las cosas nos llevan al punto de cero argumentos.

Cero argumentos en constructor

Es posible que haya notado la eighty_fourfunción en mi explicación.

int eighty_four();

Sí, esto también se ve afectado por el análisis más irritante. Es una definición válida, y una que probablemente haya visto si creó archivos de encabezado (y debería). Agregar paréntesis no lo arregla.

int eighty_four(());

¿Por qué es así? Bueno, ()no es una expresión. En C ++, debe poner una expresión entre paréntesis. No puede escribir auto value = ()en C ++, porque ()no significa nada (e incluso si lo hiciera, como una tupla vacía (ver Python), sería un argumento, no cero). Prácticamente eso significa que no puede usar la sintaxis abreviada sin usar la sintaxis de C ++ 11 {}, ya que no hay expresiones para poner entre paréntesis, y la gramática de C para las declaraciones de funciones siempre se aplicará.

Konrad Borowski
fuente
12

Podrías en cambio

A a(());

utilizar

A a=A();
usuario265149
fuente
32
La 'mejor solución' no es equivalente. int a = int();se inicializa acon 0, int a;deja sin ainicializar. Una solución alternativa correcta es usar los A a = {};agregados, A a;cuando la inicialización predeterminada hace lo que desea, y A a = A();en todos los demás casos, o simplemente usar de manera A a = A();consistente. En C ++ 11, solo useA a {};
Richard Smith
6

Los paréntesis más internos en su ejemplo serían una expresión, y en C ++ la gramática define un expressionpara ser uno assignment-expressionu otro expressionseguido de una coma y otro assignment-expression(Apéndice A.4 - Resumen / Expresiones de gramática).

La gramática define además assignment-expressioncomo uno de varios otros tipos de expresión, ninguno de los cuales puede ser nada (o solo espacios en blanco).

Entonces, la razón que no puede tener A a(())es simplemente porque la gramática no lo permite. Sin embargo, no puedo responder por qué las personas que crearon C ++ no permitieron este uso particular de parens vacías como algún tipo de caso especial; supongo que preferirían no poner un caso tan especial si hubiera Una alternativa razonable.

Michael Burr
fuente