Error de plantilla confusa

91

He estado jugando con clang por un tiempo, y me encontré con "test / SemaTemplate /pendent-template-recovery.cpp" (en la distribución de clang) que se supone que proporciona pistas para recuperarse de un error de plantilla.

Todo se puede reducir fácilmente a un ejemplo mínimo:

template<typename T, typename U, int N> struct X {
    void f(T* t)
    {
        // expected-error{{use 'template' keyword to treat 'f0' as a dependent template name}}
        t->f0<U>();
    }
};

El mensaje de error producido por clang:

tpl.cpp:6:13: error: use 'template' keyword to treat 'f0' as a dependent template name
         t->f0<U>();
            ^
            template 
1 error generated.

... Pero me cuesta entender dónde se supone exactamente que se debe insertar la templatepalabra clave para que el código sea sintácticamente correcto.

Prasoon Saurav
fuente
11
¿Intentaste insertarlo donde apunta la flecha?
Mike Seymour
3
Similar a esto y a esto
Prasoon Saurav

Respuestas:

104

ISO C ++ 03 14.2 / 4:

Cuando el nombre de una especialización de plantilla de miembro aparece después. o -> en una expresión-postfijo, o después del especificador-nombre-anidado en un id-calificado, y la expresión-postfijo o id-calificado depende explícitamente de un parámetro-plantilla (14.6.2), el nombre de la plantilla del miembro debe prefijado por la plantilla de palabras clave . De lo contrario, se asume que el nombre indica una no plantilla.

En t->f0<U>(); f0<U>es una especialización de plantilla de miembro que aparece después ->y que depende explícitamente del parámetro de plantilla U, por lo que la especialización de plantilla de miembro debe tener el prefijo de templatepalabra clave.

Así que cámbiate t->f0<U>()a t->template f0<U>().

Prasoon Saurav
fuente
Curiosamente, pensé que poner la expresión entre paréntesis: t->(f0<U>())lo habría solucionado, como pensé que lo pondría f0<U>()en una expresión independiente ... bueno, pensé mal, parece ...
24
¿Podrías comentar por qué es así? ¿Por qué C ++ requeriría este tipo de sintaxis?
Curioso
2
Sí, esto es extraño. El idioma puede "detectar" que la palabra clave de la plantilla debe estar presente. Si puede hacer eso, entonces debería simplemente "insertar" la palabra clave allí.
Enrico Borba
26

Además de los puntos que hicieron otros, observe que a veces el compilador no puede tomar una decisión y ambas interpretaciones pueden producir programas válidos alternativos al crear instancias

#include <iostream>

template<typename T>
struct A {
  typedef int R();

  template<typename U>
  static U *f(int) { 
    return 0; 
  }

  static int f() { 
    return 0;
  }
};

template<typename T>
bool g() {
  A<T> a;
  return !(typename A<T>::R*)a.f<int()>(0);
}


int main() {
  std::cout << g<void>() << std::endl;
}

Se imprime 0al omitir templateantes f<int()>pero 1al insertarlo. Lo dejo como ejercicio para averiguar qué hace el código.

Johannes Schaub - litb
fuente
3
¡Ese es un ejemplo diabólico!
Matthieu M.
1
No puedo reproducir el comportamiento que está describiendo en Visual Studio 2013. Siempre llama f<U>y siempre imprime 1, lo que para mí tiene mucho sentido. Todavía no entiendo por qué templatese requiere la palabra clave y qué diferencia hace.
Jirafa violeta
@Violet el compilador VSC ++ no es un compilador compatible con C ++. Se necesita una nueva pregunta si desea saber por qué VSC ++ siempre imprime 1.
Johannes Schaub - litb
1
Esta respuesta explica por qué templatese necesita: stackoverflow.com/questions/610245/… sin depender únicamente de términos estándar que son difíciles de entender. Informe si algo en esa respuesta sigue siendo confuso.
Johannes Schaub - litb
@ JohannesSchaub-litb: Gracias, una gran respuesta. Resulta que lo he leído antes porque yo ya lo voté a favor. Aparentemente, mi memoria es normalita.
Jirafa violeta
12

Insértelo justo antes del punto donde está el símbolo de intercalación:

template<typename T, typename U, int N> struct X {
     void f(T* t)
     {
        t->template f0<U>();
     }
};

Editar: la razón de esta regla se vuelve más clara si piensa como un compilador. Los compiladores generalmente solo miran hacia adelante uno o dos tokens a la vez, y generalmente no "miran hacia adelante" al resto de la expresión. [Editar: ver comentario] El motivo de la palabra clave es el mismo que el que necesita typenamepara indicar nombres de tipos dependientes: le dice al compilador "oye, el identificador que estás a punto de ver es el nombre de una plantilla, en lugar de el nombre de un miembro de datos estáticos seguido de un signo menor que ".

Doug
fuente
1
Me habría no sido capaz de adivinar que ... pero gracias ;-). ¡Claramente siempre hay algo que aprender sobre C ++!
3
Incluso con una anticipación infinita, aún lo necesitará template. Hay casos en los que tanto con como sin templateproducirán programas válidos con comportamiento diferente. Así que esto no es solo un problema sintáctico ( t->f0<int()>(0)es válido sintácticamente tanto para la versión de lista de argumentos de plantilla como para la versión menor que).
Johannes Schaub - litb
@Johannes Schaub - litb: Correcto, entonces es más un problema de asignar un significado semántico consistente a la expresión, que de mirar hacia adelante.
Doug
11

Extracto de Plantillas C ++

La construcción .template Se descubrió un problema muy similar después de la introducción de typename. Considere el siguiente ejemplo utilizando el tipo de conjunto de bits estándar:

template<int N> 
void printBitset (std::bitset<N> const& bs) 
{ 
    std::cout << bs.template to_string<char,char_traits<char>, 
                                       allocator<char> >(); 
} 

La construcción extraña en este ejemplo es .template. Sin ese uso adicional de la plantilla, el compilador no sabe que el token menor que (<) que sigue no es realmente "menor que" sino el comienzo de una lista de argumentos de plantilla. Tenga en cuenta que esto es un problema solo si la construcción antes del período depende de un parámetro de plantilla. En nuestro ejemplo, el parámetro bs depende del parámetro de plantilla N.

En conclusión, la notación .template (y notaciones similares como -> template) debe usarse solo dentro de las plantillas y solo si siguen algo que depende de un parámetro de plantilla.

Chubsdad
fuente