c++ g++ typedef typename

c++ - ¿Por qué necesito usar typedef typename en g++ pero no VS?



(5)

Había pasado un tiempo desde que GCC me atrapó con este, 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?

Para aquellos que no saben lo que quiero decir aquí hay 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 ICC), pero falla en GCC porque lo quiere así:

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, sino algo tonto que demuestra el problema.


Bueno, GCC en realidad no requiere el typedef - 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: usted 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 que ser un tipo, pero puede ser difícil para el compilador pobre (escritores).

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


Cabe señalar que el problema de kindness de valor / tipo no es el problema fundamental. El problema principal es el análisis . Considerar

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

No hay forma 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 omitir el análisis completo, solo tenga en cuenta el fragmento

(a)(b)(c)

En caso de que no lo recuerde, el elenco tiene una precedencia más alta que la invocación de función en C, una de las razones por las cuales Bjarne quería lanzar el estilo de función. Por lo tanto, no es posible decir si lo 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.

Tenga en cuenta que también se requiere la palabra clave "templatename" por la misma razón que "typename", no se pueden analizar las cosas sin conocer su clase en C / C ++.


El nombre de tipo es requerido por el estándar. 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 iterador como algo más.

Puedes verificarlo 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 la primera compilación de la plantilla, el paso de f () Test :: value se interpretó como un valor, pero la instanciación de la plantilla Test <> con el tipo X produce un tipo.


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 :: iterator fuera un variable por ejemplo).

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


Parece que VS / ICC proporciona la palabra clave typename donde cree que es necesario. Tenga en cuenta que este es un Bad Thing (TM) - para que el compilador decida lo que 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.