Desambigador de plantilla para nombres dependientes

8

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>()?

Jodebo
fuente
Creo que es posible que se encuentre con un problema de implementación específico de clang en el que los tipos de plantillas coincidentes con los parámetros aún no se hayan
Eddy Luten el
Creo que 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.
aschepler
@EddyLuten P0522 trata sobre parámetros de plantilla de plantilla, y no hay ninguno en este ejemplo.
Aschepler
Un ejemplo simplificado que parece estar relacionado: godbolt.org/z/KpXpYs
Evg

Respuestas:

1

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:

En todos los demás contextos, al nombrar una especialización de plantilla de un miembro de una especialización desconocida ( [temp.dep.type] ), el nombre de la plantilla del miembro deberá ir precedido de la palabra clave template.

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:

Un tipo es dependiente si es

  • un parámetro de plantilla,
  • un miembro de una especialización desconocida,
  • una clase o enumeración anidada que es un miembro dependiente de la instanciación actual,
  • Un tipo calificado por CV donde el tipo no calificado por CV es dependiente,
  • un tipo compuesto construido a partir de cualquier tipo dependiente,
  • un tipo de matriz cuyo tipo de elemento depende o cuyo límite (si lo hay) depende del valor,
  • un tipo de función cuya especificación de excepción depende del valor,
  • una identificación de plantilla simple en la que el nombre de la plantilla es un parámetro de plantilla o cualquiera de los argumentos de la plantilla es un tipo dependiente o una expresión que depende del tipo o del valor o es una expansión del paquete, o
  • denotado por decltype(expresión) , donde expresión es dependiente del tipo.

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 por decltype. 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 plantilla RayDimes 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:

Una expresión id depende del tipo si contiene

  • un identificador asociado por búsqueda de nombre con una o más declaraciones declaradas con un tipo dependiente,
  • un identificador asociado por búsqueda de nombre con un parámetro de plantilla sin tipo declarado con un tipo que contiene un tipo de marcador de posición,
  • un identificador asociado por búsqueda de nombre con una variable declarada con un tipo que contiene un tipo de marcador de posición ([dcl.spec.auto]) donde el inicializador depende del tipo,
  • un identificador asociado por búsqueda de nombre con una o más declaraciones de funciones miembro de la instancia actual declarada con un tipo de retorno que contiene un tipo de marcador de posición,
  • un identificador asociado por búsqueda de nombre con una declaración de enlace estructurado cuyo inicializador de paréntesis o igual depende del tipo,
  • el identificador __func__([dcl.fct.def.general]), donde cualquier función de cierre es una plantilla, un miembro de una plantilla de clase o una lambda genérica,
  • un id de plantilla que depende,
  • una conversión-función-id que especifica un tipo dependiente, o
  • un especificador de nombre anidado o una identificación calificada que nombra a un miembro de una especialización desconocida;

o si nombra un miembro dependiente de la instanciación actual que es un miembro de datos estáticos de tipo "matriz de enlace desconocido de T" para algunos T([temp.static]).

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 de RayDimciertamente no es un parámetro de plantilla , una función miembro o una declaración de enlace estructurado, y su tipo const intciertamente no es un tipo dependiente o tipo de matriz y no contiene un tipo de marcador de posición. Por RayDimlo 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:

Una expresión id depende del valor si:

  • depende del tipo
  • es el nombre de un parámetro de plantilla que no es de tipo,
  • nombra a un miembro de datos estáticos que es un miembro dependiente de la instanciación actual y no se inicializa en un declarador de miembros ,
  • nombra una función miembro estática que es un miembro dependiente de la instanciación actual, o
  • es una constante con tipo literal y se inicializa con una expresión que depende del valor.

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 inicializador 3no depende del valor.

Por RayDimlo tanto, no depende del valor ni del tipo. Por Matrix<RayDim>lo tanto, no es un tipo dependiente, yF.headno es miembro de una instancia desconocida y la templatepalabra clave anterior heades opcional, no es obligatoria. (Está permitido ya que no está en un "contexto de solo tipo" y headde hecho nombra una plantilla de miembro).

aschepler
fuente
0

Descargo de responsabilidad

esta no es una respuesta, sino un comentario largo

Mis habilidades de 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 en

return yF.template head<1>();

Nombres dependientes

A veces, dentro de las plantillas, tenemos que ayudar al compilador a decidir si un nombre se refiere a

  1. un valor int T::x = 0,
  2. un tipo struct T::x {};o
  3. una plantilla template <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 usamos template. 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.

template <int N>
struct Matrix 
{
    template <int Idx>
    int head() { return Idx; }
};


template <typename T>
struct Test 
{    
    static constexpr int RayDim = 3;

    int func() const 
    {
        Matrix<RayDim> yF;
        return yF.head<1>(); // clang complains, gcc and msvc are ok
    }
};


struct Empty {};

int test() 
{
    Test<Empty> t;
    return t.func();
}

Por RayDimlo general, debe ser un nombre dependiente (porque está dentro de la plantilla Test), lo que también podría Matrix<RayDim>ser un nombre dependiente. Por ahora, supongamos que en Matrix<RayDim>realidad es un nombre dependiente. Esto también hace Matrix<RayDim>::headun nombre dependiente. Como Matrix<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 la templatepalabra clave. Esto es de lo que se queja el ruido metálico.

Sin embargo, dado que RayDimse define dentro Testy functambién se define dentro de la misma plantilla y no una función con plantilla en sí misma, no creo que en RayDimrealidad sea un nombre dependiente en el contexto de func. Además, RayDimno se basa en los argumentos de plantilla de Test. En este caso, Matrix<RayDim>y Matrix<RayDim>::headrespectivamente, se convertirían en nombres no dependientes, lo que nos permite omitir la templatepalabra clave. Es por eso que compila gcc (y msvc).

Si tuviéramos RayDimtambién una plantilla , como aquí

template <typename>
static constexpr int RayDim = 3;

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 de Test<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 donde funcse instancia.

Timo
fuente