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() const
clang 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
template
palabra 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
template
despué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
Matrix
no es un parámetro de plantilla. El argumento de la plantillaRayDim
es 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
:RayDim
Ciertamente 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 deRayDim
ciertamente no es un parámetro de plantilla , una función miembro o una declaración de enlace estructurado, y su tipoconst int
ciertamente no es un tipo dependiente o tipo de matriz y no contiene un tipo de marcador de posición. PorRayDim
lo 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
RayDim
encuentran en el párrafo 2:Desde arriba,
RayDim
no 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 inicializador3
no depende del valor.Por
RayDim
lo tanto, no depende del valor ni del tipo. PorMatrix<RayDim>
lo tanto, no es un tipo dependiente,yF.head
no es miembro de una instancia desconocida y latemplate
palabra clave anteriorhead
es opcional, no es obligatoria. (Está permitido ya que no está en un "contexto de solo tipo" yhead
de 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
template
palabra 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
RayDim
lo 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>::head
un nombre dependiente. ComoMatrix<RayDim>::head
es 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 latemplate
palabra clave. Esto es de lo que se queja el ruido metálico.Sin embargo, dado que
RayDim
se define dentroTest
yfunc
también se define dentro de la misma plantilla y no una función con plantilla en sí misma, no creo que enRayDim
realidad sea un nombre dependiente en el contexto defunc
. Además,RayDim
no se basa en los argumentos de plantilla deTest
. En este caso,Matrix<RayDim>
yMatrix<RayDim>::head
respectivamente, se convertirían en nombres no dependientes, lo que nos permite omitir latemplate
palabra clave. Es por eso que compila gcc (y msvc).Si tuviéramos
RayDim
tambié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
RayDim
es un nombre dependiente en el contexto deTest<T>::func
o 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
RayDim
volverse dependiente en el punto dondefunc
se instancia.fuente