2010-09-24 9 views
68

He estado jugando con clang un tiempo, y me encontré con "prueba/SemaTemplate/dependent-template-recover.cpp" (en la distribución clang) que se supone que proporciona sugerencias para recuperar de una plantilla error.error de plantilla confusa

Todo esto se puede extraer fácilmente abajo a un mínimo ejemplo:

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 ruido metálico:

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

... Pero tengo dificultades para comprender dónde exactamente Se supone que uno debe insertar la palabra clave template para que el código sea sintácticamente correcto.

+9

¿Intentó insertarlo donde apunta la flecha? –

+3

Similar a [esto] (http://stackoverflow.com/questions/3691420/compiler-error-when-using-integer-as-template-parameter/) y [this] (http://stackoverflow.com/questions/3621719/c-template-syntax) –

Respuesta

65

ISO C++ 03 14.2/4:

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

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

Así que cambie t->f0<U>() a t->template f0<U>().

+0

Curiosamente, pensé en poner la expresión entre paréntesis: 't -> (f0 ())' lo habría arreglado, ya que pensé que pondría 'f0 ()' en independiente expresión ... bueno, pensé mal, parece ... –

+5

¿Podrías quizás comentar por qué este es el caso? ¿Por qué C++ requeriría este tipo de sintaxis? – Curious

7

insertarla justo antes del punto en que se encuentra el cursor:

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

edición: la razón de esta regla se hace más clara si se piensa como un compilador. Generalmente, los compiladores 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 por qué necesita la palabra clave typename para indicar nombres de tipos dependientes: le dice al compilador "hey, el identificador que está a punto de ver es el nombre de una plantilla, en lugar del nombre de un miembro de datos estáticos seguido de un signo menor que ".

+0

Hubiera ** nunca ** podido adivinar eso ... pero gracias ;-). ¡Claramente siempre hay algo que aprender sobre C++! –

+3

Incluso con un look-ahead infinito, todavía necesitará 'template'. Hay casos en que tanto con y sin 'template' arrojarán programas válidos con un comportamiento diferente. Por lo tanto, esto no es solo un problema sintáctico ('t-> f0 (0)' es válido sintácticamente para la versión de la lista de argumentos menor que la plantilla). –

+0

@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

6

Extracto de C++ Templates

El .template Construct Un problema muy similar fue descubierto después de la introducción del nombre de tipo. 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 plantilla, el compilador no sabe que el token (<) 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 anotaciones similares como -> plantilla) debe usarse solo dentro de plantillas y solo si siguen algo que depende en un parámetro de plantilla.

+0

de hecho, +1 para el excelente ejemplo –

19

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

#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; 
} 

Esto imprime 0 al omitir template antes f<int()> pero 1 al insertarlo. Lo dejo como ejercicio para descubrir qué hace el código.

+1

¡Ese es un ejemplo diabólico! –

+1

No puedo reproducir el comportamiento que está describiendo en Visual Studio 2013. Siempre llama a 'f ' y siempre imprime '1', lo cual tiene mucho sentido para mí. Todavía no entiendo por qué se requiere la palabra clave 'template' y qué diferencia hace. –

+0

@Violet el compilador VSC++ no es un compilador C++ compatible. Se necesita una nueva pregunta si desea saber por qué VSC++ siempre imprime 1. –

Cuestiones relacionadas