Intentando entender plantillas y búsqueda de nombres

9

Estoy tratando de entender los siguientes fragmentos de código

Fragmento n. ° 1

template <typename T>
struct A
{
    static constexpr int VB = T::VD;
};

struct B : A<B>
{
};

Ni gcc9 ni clang9 arrojan un error aquí.

P. ¿Por qué se compila este código? ¿No estamos creando instancias A<B>cuando heredamos de B? No hay VD en B, entonces, ¿no debería el compilador arrojar un error aquí?

Fragmento # 2

template <typename T>
struct A
{
    static constexpr auto AB = T::AD; // <- No member named AD in B
};

struct B : A<B>
{
    static constexpr auto AD = 0xD;
};

En este caso, gcc9 compila bien pero clang9 arroja un error que dice "Ningún miembro llamado AD en B".

P. ¿Por qué se compila con gcc9 / por qué no se compila con clang9?

Fragmento # 3

template <typename T>
struct A
{
    using TB = typename T::TD;
};

struct B : A<B>
{
    using TD = int;
};

Aquí tanto clang9 como gcc9 arrojan un error. gcc9 dice "uso no válido del tipo incompleto 'struct B'".

P. Si la estructura B está incompleta aquí, ¿por qué no está incompleta en el fragmento n. ° 2?

Opciones del compilador usado: -std=c++17 -O3 -Wall -Werror. ¡¡¡Gracias por adelantado!!!

Efecto secundario mutable
fuente
@xception ¿No está creando struct Binstancias Acon B?
Efecto secundario mutable
clang9 arroja un error que dice "Ningún miembro llamado AD en B" . como Bestá incompleto ... Pero no
estoy
@MutableSideEffect oh sí, mi mal,
léalo
@ Jarod42 entonces ¿por qué compila bien gcc?
Efecto secundario mutable
1
He marcado esta pregunta como "necesita más atención" y la pregunta de hecho contiene más de una pregunta (de ahí mi conclusión), entonces, ¿por qué mi bandera está equivocada?
Dominique

Respuestas:

4

Creo que estos esencialmente se reducen a [temp.inst] / 2 (énfasis mío):

La creación de instancias implícita de una especialización de plantilla de clase provoca la creación de instancias implícita de las declaraciones, pero no de las definiciones , argumentos predeterminados o especificadores sin excepción de las funciones de miembros de clase, clases de miembros, enumeraciones de miembros con ámbito, miembros de datos estáticos , plantillas de miembros y amigos; [...]

y [temp.inst] / 9

Una implementación no debe instanciar implícitamente [...] un miembro de datos estáticos de una plantilla de clase [...] a menos que se requiera dicha instanciación.

La redacción de la norma relativa a la creación de instancias de plantilla implícita deja muchos detalles abiertos a interpretación. En general, me parece que simplemente no puede confiar en partes de una plantilla que no se instancian a menos que la especificación lo diga explícitamente. Así:

Fragmento n. ° 1

P. ¿Por qué se compila este código? ¿No estamos creando instancias de A cuando heredamos de B? No hay VD en B, entonces, ¿no debería el compilador arrojar un error aquí?

Estás creando instancias A<B>. Pero la creación de instancias A<B>solo crea instancias de las declaraciones, no las definiciones de sus miembros de datos estáticos. VBnunca se usa de una manera que requiera una definición para existir. El compilador debe aceptar este código.

Fragmento # 2

P. ¿Por qué se compila con gcc9 / por qué no se compila con clang9?

Como señaló Jarod42, la declaración de ABcontiene un tipo de marcador de posición. Me parece que la redacción de la norma no es realmente clara sobre lo que se supone que sucederá aquí. ¿La instanciación de la declaración de un miembro de datos estáticos que contiene un tipo de marcador de posición activa la deducción del tipo de marcador de posición y, por lo tanto, constituye un uso que requiere la definición del miembro de datos estático? No puedo encontrar una redacción en el estándar que diga claramente sí o no a eso. Por lo tanto, diría que ambas interpretaciones son igualmente válidas aquí y, por lo tanto, GCC y clang tienen razón ...

Fragmento # 3

P. Si la estructura B está incompleta aquí, ¿por qué no está incompleta en el fragmento n. ° 2?

Un tipo de clase solo se completa en el punto en el que llega al cierre }del especificador de clase [class.mem] / 6 . Por lo tanto, Bestá incompleto durante la instanciación implícita de A<B>todos sus fragmentos. Es solo que esto era irrelevante para Snippet # 1. En Snippet # 2, clang te dio un error No member named AD in Bcomo resultado. Similar al caso del Snippet # 2, no puedo encontrar la redacción sobre cuándo se instanciarían exactamente las declaraciones de alias de miembro. Sin embargo, a diferencia de la definición de miembros de datos estáticos, no existe una redacción para evitar explícitamente la creación de instancias de declaraciones de alias de miembros durante la creación de instancias implícita de una plantilla de clase. Por lo tanto, diría que el comportamiento de GCC y clang es una interpretación válida del estándar en este caso ...

Michael Kenzel
fuente
Gracias. En este caso, inicializamos el miembro de datos estáticos dentro del cuerpo. ¿Es la inicialización parte de la declaración o parte de la definición del miembro de datos estáticos? Tenía la impresión de que si la inicialización está dentro del cuerpo, entonces eso es parte de la declaración del miembro de datos estáticos. Si es parte de la declaración, entonces su primera cita requiere una instanciación inmediata como parte de la instanciación implícita circundante de la plantilla de clase.
Johannes Schaub - litb
Miré la especificación y parece que hay una diferencia entre C ++ 14 y C ++ 17 aquí. En C ++ 14, el constexprmiembro de datos estáticos era solo una declaración. C ++ 17 obtuvo inlinevariables e constexprimplica inline, y esto hace que la declaración del miembro de datos estáticos en el cuerpo sea una definición.
Johannes Schaub - litb
eel.is/c++draft/dcl.spec.auto#4.sentence-2 dice "El tipo de una variable declarada usando un tipo de marcador de posición se deduce de su inicializador. Este uso está permitido en una declaración de inicialización ([dcl. init]) de una variable ". Por lo tanto, argumentaría que se requiere la definición del miembro de datos estáticos, ya que contiene el inicializador.
Johannes Schaub - litb
@ JohannesSchaub-litb ¡Gracias por investigar esto! También me he estado preguntando acerca de estas preguntas, pero no pude encontrar ninguna redacción que fuera concluyente. Con respecto a la inicialización frente a la definición, considere una definición de una función miembro dentro de la definición de la plantilla de clase. Tal definición es también una declaración y no hay otras declaraciones. Sin embargo, la instanciación implícita de la plantilla de clase solo instanciará una declaración, pero no la definición de la función miembro. ¿Por qué no sería lo mismo para los miembros de datos estáticos?
Michael Kenzel
Si no hay nada que requiera la definición, la definición no se instancia. Pero en el autocaso, la regla dice que la declaración debe ser una declaración de inicialización. Este solo puede ser el caso si se sabe que la declaración es una definición (que yo sepa ... he estado fuera de la tierra de los abogados por un tiempo). En el pasado, había un caso similar y se agregó eel.is/c++draft/temp.inst#2.sentence-3 , donde una declaración que es una definición se instancia como "se sabe que es una definición" sin realmente instanciando la definición.
Johannes Schaub - litb