El programa se compila de manera diferente en 3 compiladores principales de C ++. ¿Cuál es la correcta?

116

Como seguimiento interesante (aunque no de gran importancia práctica) a mi pregunta anterior: ¿Por qué C ++ nos permite rodear el nombre de la variable entre paréntesis cuando declaramos una variable?

Descubrí que combinar la declaración entre paréntesis con la característica de nombre de clase inyectada puede llevar a resultados sorprendentes con respecto al comportamiento del compilador.

Eche un vistazo al siguiente programa:

#include <iostream>
struct B
{
};

struct C
{
  C (){ std::cout << "C" << '\n'; }
  C (B *) { std::cout << "C (B *)" << '\n';}
};

B *y = nullptr;
int main()
{
  C::C (y);
}
  1. Compilar con g ++ 4.9.2 me da el siguiente error de compilación:

    main.cpp:16:10: error: cannot call constructor 'C::C' directly [-fpermissive]
  2. Se compila con éxito con MSVC2013 / 2015 e imprime C (B *)

  3. Se compila con éxito con clang 3.5 e imprime C

Entonces, la pregunta obligatoria es ¿cuál es la correcta? :)

(Sin embargo, me incliné fuertemente hacia la versión clang y la forma msvc de dejar de declarar la variable después de simplemente cambiar el tipo con técnicamente su typedef parece un poco extraño)

Predelnik
fuente
3
C::C y;no tiene sentido, ¿verdad? Tampoco C::C (y); Al principio pensé que esta era una instancia de Most-Vexing-Parse stackoverflow.com/questions/tagged/most-vexing-parse , pero ahora creo que es solo un comportamiento indefinido, lo que significa que los tres compiladores son "correctos".
Dale Wilson
4
# 3 clang es definitivamente incorrecto, # 2 msvc es demasiado permisivo y # 1 g ++ es correcto ((supongo)
8
C::Cno nombra un tipo, nombra una función, por lo que GCC tiene razón en mi opinión.
Galik

Respuestas:

91

GCC es correcto, al menos de acuerdo con las reglas de búsqueda de C ++ 11. 3.4.3.1 [class.qual] / 2 especifica que, si el especificador de nombre anidado es el mismo que el nombre de la clase, se refiere al constructor, no al nombre de la clase inyectada. Da ejemplos:

B::A ba;           // object of type A
A::A a;            // error, A::A is not a type name
struct A::A a2;    // object of type A

Parece que MSVC lo malinterpreta como una expresión de conversión de estilo de función creando un temporal Ccon ycomo parámetro de constructor; y Clang lo malinterpreta como una declaración de una variable llamada yde tipo C.

Mike Seymour
fuente
2
Sí, 3.4.3.1/2 es la clave. ¡Buen trabajo!
Lightness Races in Orbit
Dice "En una búsqueda en la que no se ignoran los nombres de las funciones". Me parece que en los ejemplos dados, en particular A::A a;, los nombres de las funciones deben ignorarse, ¿o no?
Columbo
1
Siguiendo la numeración en N4296, la clave es realmente 3.4.3.1/2.1: "si el nombre especificado después del especificador de nombre anidado, cuando se busca en C, es el nombre de clase inyectado de C [...] en cambio, se considera que el nombre nombra al constructor de la clase C. " Sin embargo, el resumen de Mike está un poco demasiado simplificado; por ejemplo, una typedef del nombre de clase dentro de la clase permitiría que un especificador de nombre anidado diferente del nombre de la clase aún se refiera al nombre de la clase, por lo que aún se referiría al ctor.
Jerry Coffin
2
@Mgetz: De la pregunta: "Se compila correctamente con MSVC2013 / 2015 e imprime C (B *)" .
Lightness Races in Orbit
2
Para que sea más completo, esto debe aclarar si está mal formado y requiere diagnóstico, o mal formado sin diagnóstico requerido. Si es lo último, entonces todos los compiladores son "correctos".
MM
16

G ++ es correcto ya que da un error. Porque no se puede llamar directamente al constructor en tal formato sin newoperador. Y aunque su código llama C::C, parece una llamada de constructor. Sin embargo, de acuerdo con el estándar 3.4.3.1 de C ++ 11, esta no es una llamada de función legal o un nombre de tipo ( consulte la respuesta de Mike Seymour ).

Clang está mal ya que ni siquiera llama a la función correcta.

MSVC es algo razonable, pero aún no sigue el estándar.

Kun Ling
fuente
2
¿Qué newcambia el operador?
Neil Kirk
1
@NeilKirk: Mucho, para las personas que piensan que new B(1,2,3)es una especie de "llamada directa al constructor" (que, por supuesto, no lo es) a diferencia de la instanciación temporal B(1,2,3)o la declaración B b(1,2,3).
Lightness Races in Orbit
@LightningRacisinObrit ¿Cómo describiría lo que new B(1,2,3)es?
user2030677
1
@ user2030677: una nueva expresión que utiliza la palabra clave new, un nombre de tipo y una lista de argumentos del constructor. Todavía no es una "llamada directa al constructor".
Lightness Races in Orbit
"Clang está equivocado ya que ni siquiera llama a la función correcta": Creo (debido a la observación del OP sobre los paréntesis en las declaraciones) que Clang interpreta C::C (y); como C::C y;, es decir, una definición de una variable y de tipo C (usando el tipo inyectado C: : C mientras se ignora erróneamente la especificación del lenguaje cada vez más loco 3.4.1,2 que hace que C :: C sea el constructor). Eso no es un error tan flagrante como parece pensar, en mi opinión.
Peter - Reincorpora a Monica