2009-03-13 4 views
45

Ha pasado un tiempo desde que GCC me atrapó con esta, pero simplemente sucedió hoy. Pero nunca he entendido por qué GCC requiere typedef typename dentro de las plantillas, mientras que VS y creo que ICC no lo hace. ¿Es typedef typename thing un "error" o un estándar de restricción excesiva, o algo que queda en manos de los escritores del compilador?¿Por qué necesito usar typedef typename en g ++ pero no VS?

Para aquellos que no saben lo que quiero decir aquí es una muestra:

template<typename KEY, typename VALUE> 
bool find(const std::map<KEY,VALUE>& container, const KEY& key) 
{ 
    std::map<KEY,VALUE>::const_iterator iter = container.find(key); 
    return iter!=container.end(); 
} 

El código anterior se compila en VS (y probablemente en CPI), pero falla en GCC, ya que quiere de esta manera:

template<typename KEY, typename VALUE> 
bool find(const std::map<KEY,VALUE>& container, const KEY& key) 
{ 
    typedef typename std::map<KEY,VALUE>::const_iterator iterator; //typedef typename 
    iterator iter = container.find(key); 
    return iter!=container.end(); 
} 

Nota: Esta no es una función real que estoy usando, pero sólo algo tonto que demuestra el problema.

+4

La razón por la que se necesita en g ++, es porque g ++ es más compatible con el estándar. VS fue un poco flojo en esta parte del análisis de templacionización (que ha llevado a otros problemas en plantillas más complejas). –

+0

Sí, pero ¿por qué el friggin estándar hace esto? ¡He tratado un código idéntico! –

Respuesta

52

El nombre de tipo es requerido por la norma. La compilación de plantillas requiere una verificación en dos pasos. Durante la primera pasada, el compilador debe verificar la sintaxis de la plantilla sin proporcionar realmente las sustituciones de tipo. En este paso, se asume que std :: map :: iterator es un valor. Si denota un tipo, se requiere la palabra clave typename.

¿Por qué es esto necesario? Antes de sustituir los tipos reales de CLAVE y VALOR, el compilador no puede garantizar que la plantilla no esté especializada y que la especialización no redefine la palabra clave del iterador como algo más.

Puede comprobarlo con este código:

class X {}; 
template <typename T> 
struct Test 
{ 
    typedef T value; 
}; 
template <> 
struct Test<X> 
{ 
    static int value; 
}; 
int Test<X>::value = 0; 
template <typename T> 
void f(T const &) 
{ 
    Test<T>::value; // during first pass, Test<T>::value is interpreted as a value 
} 
int main() 
{ 
    f(5); // compilation error 
    X x; f(x); // compiles fine f: Test<T>::value is an integer 
} 

La última llamada falla con un error que indica que durante el primer paso de compilación plantilla de f() Test :: valor se interpreta como un valor, pero instanciación de la plantilla Prueba <> con el tipo X produce un tipo.

+3

Buen ejemplo de caso de falla –

+2

Creo que confundió sus comentarios sobre las dos llamadas a 'f',' f (X()); 'tiene éxito mientras' f (5); 'es un error de compilación. De todos modos, MSVC maneja esto bien - parece retrasar la decisión de si 'Test :: value' es un valor o un tipo hasta que la plantilla haya sido instanciada. No hace esto para los miembros de una plantilla de clase, sin embargo. –

+0

@Sumudu: tiene razón, también corrigí la llamada 'f (X())' al código más explícito anterior. Si MSVC demora el control hasta que se cree una instancia del tipo, entonces MSVC no cumple con el estándar. –

31

Bueno, GCC en realidad no requiere requieretypedef - typename es suficiente. Esto funciona:

#include <iostream> 
#include <map> 

template<typename KEY, typename VALUE> 
bool find(const std::map<KEY,VALUE>& container, const KEY& key) 
{ 
    typename std::map<KEY,VALUE>::const_iterator iter = container.find(key); 
    return iter!=container.end(); 
} 

int main() { 
    std::map<int, int> m; 
    m[5] = 10; 
    std::cout << find(m, 5) << std::endl; 
    std::cout << find(m, 6) << std::endl; 
    return 0; 
} 

Este es un ejemplo de un problema de análisis sensible al contexto. Lo que significa la línea en cuestión no es aparente solo a partir de la sintaxis en esta función; necesita saber si std::map<KEY,VALUE>::const_iterator es un tipo o no.

Ahora, no puedo pensar en un ejemplo de lo que ... ::const_iterator podría ser, excepto un tipo, que tampoco sería un error. Así que supongo que el compilador puede descubrir que tiene como tipo, pero puede ser difícil para el compilador deficiente (escritores).

La norma requiere el uso de typename aquí, de acuerdo con la sección 14.6/3 de la norma.

+0

Aquí, también, creo que te refieres a KEY, VALUE en la línea que comienza con "typename". Con ese cambio, compila para mí. :) – unwind

+0

Vi tu comentario sobre la pregunta y la solucioné justo ahora :) –

+0

sí, de hecho es obligatorio. para una referencia vea 14.6/3 –

4

Parece que VS/ICC proporciona la palabra clave typename siempre que piensa que es obligatorio. Tenga en cuenta que esto es una mala cosa (TM) - para que el compilador decida qué usted quiere. Esto complica aún más el problema al infundir el mal hábito de omitir el typename cuando es necesario y es una pesadilla de portabilidad. Este definitivamente no es el comportamiento estándar. Pruebe en el modo estándar estricto o Comeau.

+5

Sería una mala cosa si el compilador lo hiciera de cualquier manera. De hecho, está haciendo esto solo en código roto. En realidad, no hay prohibición en el estándar contra la compilación de código roto. Sin embargo, debería ser una advertencia (diagnóstico). – MSalters

3

Este es un error en el compilador de Microsoft C++: en su ejemplo, std :: map :: iterator podría no ser un tipo (podría haber especializado std :: map en KEY, VALUE para que std :: map: : el iterador era una variable, por ejemplo).

GCC obliga a escribir el código correcto (aunque lo que quería decir es obvio), mientras que el compilador de Microsoft adivina correctamente lo que usted quiso decir (aunque el código que escribió era incorrecto).

+1

En realidad, parece que MSVC comprobará si std :: map :: iterator es un tipo o no antes de decidir. No tengo una copia del estándar, pero esto parece un comportamiento no conforme, pero solo significa que intentará (intentará) corregir y compilar algunos programas incorrectos, no introducir errores en los correctos. –

+1

Sí, es un error porque el compilador no emite un diagnóstico de código ilegal. –

+2

No existe el código ilegal. Solo se requiere un diagnóstico si el programa está mal formado. – Yttrill

2

Debe tenerse en cuenta que el problema de kindness de valor/tipo no es el problema fundamental. El problema principal es al analizar. Considere

template<class T> 
void f() { (T::x)(1); } 

No hay manera de saber si se trata de una conversión o una llamada a función a menos que la palabra clave typename sea obligatoria. En ese caso, el código anterior contiene una llamada de función. En general, la elección no se puede retrasar sin renunciar a analizar por completo, solamente se considera fragmento

(a)(b)(c) 

En caso de que no recuerde, fundido tiene una prioridad más alta que la función de llamada en C, una razón Bjarne quería estilo función arroja. Por tanto, no es posible decir si el anterior significa

(a)(b) (c) // a is a typename 

o

(a) (b)(c) // a is not a typename , b is 

o

(a)(b) (c) // neither a nor b is a typename 

donde Inserté espacio para indicar la agrupación.

También se necesita la palabra clave "templatename" por el mismo motivo que "typename", no se pueden analizar las cosas sin conocer su clase en C/C++.

+0

MSVC utiliza una solución increíblemente simple para este problema: no analiza el código dentro de una función de plantilla hasta que la plantilla se instancia con una IMO específica que es una solución mucho más agradable para los desarrolladores que requiere mucho más "esto->" , palabras clave "typename" y "template", y muchos typedefs adicionales para redefinir nombres que * ya están definidos * en una clase base. (Sí, sí, lo sé, MSVC no es estándar, pero es más fácil de usar). – Qwertie

+0

Sin embargo, ese comportamiento lo expone a la posibilidad de que la semántica de dos instancias distintas sea más diferente de lo que permiten las reglas de C++ pobres existentes. – Yttrill

+0

Cierto, pero después de pasar docenas de horas convirtiendo el código de mi plantilla en el correcto C++ estándar agregando mucho ruido sintáctico, mientras desconcertaba los mensajes de error inútiles de GCC (algunos de mis favoritos: "declaración de 'operator =' como no función" , "muy pocas listas de parámetros de plantilla", "expresión primaria esperada antes de '>' símbolo") ... He aborrecido las reglas oficiales de C++. – Qwertie

Cuestiones relacionadas