Encontré el siguiente problema al compilar el siguiente ejemplo:
template <int N>
class Matrix {
public:
template <int Idx>
int head() {
return Idx;
}
};
template <typename T>
class Test {
static constexpr int RayDim = 3;
public:
int func() const {
Matrix<RayDim> yF;
return yF.head<1>();
// ^ is template keyword required here?
}
};
struct Empty {};
void test() {
Test<Empty> t;
}
Enlace al Compiler Explorer: https://godbolt.org/z/js4XaP
El código se compila con GCC 9.2 y MSVC 19.22 pero no con clang 9.0.0. Clang afirma que se requiere una palabra clave de plantilla. Si static constexpr int RayDim = 3;se mueve a int func() constclang lo acepta.
Como se indica como comentario en el bloque de código, ¿se requiere la palabra clave de plantilla yF.head<1>()?

Matrix<RayDim>no es un tipo dependiente, por lo que no se requiere la palabra clave. Puede que tenga tiempo para una respuesta más tarde.Respuestas:
La
templatepalabra clave no debe ser requerida aquí, por lo que clang es incorrecto para rechazar el programa.Todos los números y citas de sección y párrafo estándar de C ++ a continuación son los mismos para C ++ 17 draft N4659 y el actual proyecto C ++ 20 vinculado.
El requisito de que
templatedespués de un.o->o::testigo cuando nombrar una plantilla miembro está en [temp.names] / 4 . El párrafo primero enumera los casos en que la palabra clave no está permitida, luego los casos en los que es opcional y no hace la diferencia, luego:Un "miembro de una especialización desconocida" es un miembro de un tipo dependiente que no es "la instanciación actual". Entonces la pregunta es si
Matrix<RayDim>es un tipo dependiente. Para eso, miramos [temp.dep.type] / 9:Matrix<RayDim>claramente no es un parámetro de plantilla, ningún tipo de miembro, calificado para cv, un tipo de matriz, un tipo de función o especificado pordecltype. Es un tipo compuesto, pero utiliza solo un nombre de plantilla y una expresión, por lo que no se construye a partir de ningún otro tipo.Eso deja el caso simple-template-id . El nombre de la plantilla
Matrixno es un parámetro de plantilla. El argumento de la plantillaRayDimes una expresión, así que ahora para ver si depende del tipo o del valor."Depende del tipo" se define en [temp.dep.expr] . Solo el párrafo 3 puede aplicarse a un identificador solitario como
RayDim:RayDimCiertamente no contiene ninguna__func__, plantilla-id , conversión de la función-id , anidada-nombre-especificador o cualificado-id . La búsqueda de nombre encuentra la declaración de miembro estático de la plantilla de clase. Esa declaración deRayDimciertamente no es un parámetro de plantilla , una función miembro o una declaración de enlace estructurado, y su tipoconst intciertamente no es un tipo dependiente o tipo de matriz y no contiene un tipo de marcador de posición. PorRayDimlo tanto, no depende del tipo."Depende del valor" se define en [temp.dep.constexpr] . Los únicos casos que pueden aplicarse a un identificador solitario como se
RayDimencuentran en el párrafo 2:Desde arriba,
RayDimno depende del tipo. Ciertamente no es un parámetro de plantilla o una función miembro. Es un miembro de datos estáticos y miembro dependiente de la instanciación actual, pero se inicializa en el declarador de miembros . Es decir, el "= 3" aparece dentro de la definición de clase, no en una definición de miembro separada. Es una constante con tipo literal, pero su inicializador3no depende del valor.Por
RayDimlo tanto, no depende del valor ni del tipo. PorMatrix<RayDim>lo tanto, no es un tipo dependiente,yF.headno es miembro de una instancia desconocida y latemplatepalabra clave anteriorheades opcional, no es obligatoria. (Está permitido ya que no está en un "contexto de solo tipo" yheadde hecho nombra una plantilla de miembro).fuente
Descargo de responsabilidad
esta no es una respuesta, sino un comentario largo
Mis habilidades de abogado de idiomas son demasiado bajas para entender completamente el estándar, pero aquí hay algunas cosas que descubrí al experimentar con el código. Todo lo que sigue se basa en mi comprensión (lejos de ser perfecta) del asunto y probablemente necesite algunas revisiones.
Investigación
En primer lugar, fui a una versión estándar completamente definida (C ++ 17), por lo que operamos en una implementación bien definida.
Al mirar este código , parece que MSVC todavía tiene algunos problemas (¿con su búsqueda, supongo?) Cuando se trata de creación de instancias y redefinición de plantillas. No confiaría tanto en MSVC en nuestro escenario.
Dicho esto, pensemos por qué podríamos necesitar la
templatepalabra clave enNombres dependientes
A veces, dentro de las plantillas, tenemos que ayudar al compilador a decidir si un nombre se refiere a
int T::x = 0,struct T::x {};otemplate <typename U> T::foo<U>();Si nos referimos a un valor, no hacemos nada. Si nos referimos a un tipo, tenemos que usar
typename. Y si nos referimos a una plantilla que usamostemplate. Más sobre ese tema se puede encontrar aquí .No entiendo la especificación estándar cuando se trata de la definición real de un nombre dependiente, pero aquí hay algunas observaciones.
Observaciones
Veamos el código de referencia.
Por
RayDimlo general, debe ser un nombre dependiente (porque está dentro de la plantillaTest), lo que también podríaMatrix<RayDim>ser un nombre dependiente. Por ahora, supongamos que enMatrix<RayDim>realidad es un nombre dependiente. Esto también haceMatrix<RayDim>::headun nombre dependiente. ComoMatrix<RayDim>::heades una función con plantilla, es una plantilla en sí misma y se aplican las reglas de los nombres dependientes de arriba, lo que requiere que usemos latemplatepalabra clave. Esto es de lo que se queja el ruido metálico.Sin embargo, dado que
RayDimse define dentroTestyfunctambién se define dentro de la misma plantilla y no una función con plantilla en sí misma, no creo que enRayDimrealidad sea un nombre dependiente en el contexto defunc. Además,RayDimno se basa en los argumentos de plantilla deTest. En este caso,Matrix<RayDim>yMatrix<RayDim>::headrespectivamente, se convertirían en nombres no dependientes, lo que nos permite omitir latemplatepalabra clave. Es por eso que compila gcc (y msvc).Si tuviéramos
RayDimtambién una plantilla , como aquígcc también lo trataría como un nombre dependiente (lo cual es correcto, ya que puede haber una especialización de plantilla más adelante, por lo que no lo sabemos en ese momento). Mientras tanto, msvc acepta felizmente todo lo que le arrojamos.
Conclusión
Parece que se reduce a si
RayDimes un nombre dependiente en el contexto deTest<T>::funco no. Clang cree que sí, gcc no. De alguna prueba más, parece que msvc está del lado del sonido metálico en este caso. Pero también está haciendo algo propio, así que ¿quién sabe?Me pondría del lado de gcc aquí ya que no veo ninguna forma posible de
RayDimvolverse dependiente en el punto dondefuncse instancia.fuente