El análisis más fastidioso: ¿por qué A no (()); ¿trabajo?

Entre las muchas cosas que Stack Overflow me ha enseñado es lo que se conoce como el “parse 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, parece ser intuitivamente la statement de un objeto a de tipo A , tomando un objeto B temporal como parámetro constructor, en realidad es una statement de una función que devuelve una A , tomando un puntero a una función que devuelve B y no tiene parámetros. Del mismo modo, la línea

 A a(); //declares a function 

también cae bajo 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 de B() , ya que el comstackdor lo interpretará como la statement de un objeto

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

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

 A a(()); //compile error 

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

No hay una respuesta ilustrada, es solo porque no está definida como syntax válida por el lenguaje C ++ … Así que es así, por definición del lenguaje.

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

  ((0));//compiles 

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

Para obtener más información sobre cómo se definen los idiomas y cómo funcionan los comstackdores, debe aprender acerca de la teoría del lenguaje formal o, más específicamente, Gramáticas libres de contexto (CFG) y material relacionado como máquinas de estado finito. Si le interesa que aunque las páginas de wikipedia no sean suficientes, tendrá que conseguir un libro.

La solución final a este problema es pasar a la syntax de inicialización uniforme de C + 11 si puede.

 A a{}; 

http://www.stroustrup.com/C++FAFA.html#uniform-init

Declaradores de función C

Primero que nada, hay C. En C, A a() es una statement de función. Por ejemplo, putchar tiene la siguiente statement. Normalmente, tales declaraciones se almacenan en archivos de encabezado, sin embargo, nada le impide escribirlas manualmente, si sabe cómo se ve la statement de la función. Los nombres de los argumentos son opcionales en las declaraciones, así 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 syntax legible que se parece a una llamada a función (bueno, es legible, siempre que no devuelva un puntero a la función).

 #include  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 los nombres de los argumentos en los archivos de encabezado, por lo tanto, el resultado_resultado se vería así en el archivo de encabezado.

 int output_result(int()); 

Un argumento en constructor

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

 A a(B()); 

Sí, es exactamente la misma statement de función. A es int , a es output_result , y B es int .

Puede notar fácilmente un conflicto de C con nuevas características de C ++. Para ser exactos, los constructores son nombre de clase y paréntesis, y la syntax de statement alternativa con () 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 syntax con () si falla.

Si una de esas características no existe, o tiene una syntax diferente (como {} en C ++ 11), este problema nunca habría ocurrido para la syntax con un argumento.

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

 int output_result((int())); 

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

 :1:19: error: expected declaration specifiers or '...' before '(' token 

Sin embargo, C ++ espera la 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 espera. Los paréntesis no significan nada aquí. Sin embargo, al insertar paréntesis inútiles, la statement de la función C no coincide, y la nueva syntax puede coincidir correctamente (lo que simplemente espera una expresión, como 2 + 2 ).

Más argumentos en constructor

Sin duda, un argumento es bueno, pero ¿qué tal dos? No es que los constructores tengan solo un argumento. Una de las clases incorporadas que toma dos argumentos es std::string

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

Todo está bien y bien (técnicamente, tendría un análisis más complicado si se escribiera como std::string wat(int(), char()) , pero seamos honestos, ¿quién lo escribiría? Pero supongamos que esto El código tiene un problema molesto. Supongamos que tiene que poner todo entre paréntesis.

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

No del todo.

 :2:36: error: invalid conversion from 'char' to 'const char*' [-fpermissive] In file included from /usr/include/c++/4.8/string:53:0, from :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; _Alloc = std::allocator]' [-fpermissive] basic_string<_CharT, _Traits, _Alloc>:: ^ 

No estoy seguro de por qué g ++ intenta convertir char a const char * . De cualquier manera, el constructor fue llamado con solo un valor de tipo char . No hay sobrecarga que tenga un argumento de tipo char , por lo tanto, el comstackdor 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 para 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 la expresión completa. De hecho, solo una de las expresiones debe estar entre paréntesis, ya que es suficiente con romper la gramática C ligeramente para usar la función C ++. Las cosas nos llevan al punto de cero argumentos.

Cero argumentos en el constructor

Puede haber notado la función eighty_four 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 ++, debes poner una expresión entre paréntesis. No puede escribir auto value = () en C ++, porque () no significa nada (e incluso si lo hizo, como tupla vacía (ver Python), sería un argumento, no cero). Prácticamente eso significa que no puede usar la syntax abreviada sin usar la syntax {} C ++ 11, ya que no hay expresiones para poner entre paréntesis, y la gramática C para declaraciones de funciones siempre se aplicará.

En cambio podrías

 A a(()); 

utilizar

 A a=A(); 

Los parens más internos en su ejemplo serían una expresión, y en C ++ la gramática define una expression una expression de assignment-expression u otra expression seguida de una coma y otra assignment-expression (Apéndice A.4 – Resumen gtwigtical / Expresiones).

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

Entonces, la razón por la que no puedes 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íos como una especie de caso especial. Supongo que preferirían no incluir un caso tan especial si hubiera una alternativa razonable.