web-dev-qa-db-fra.com

Pourquoi dois-je utiliser typedef typename dans g ++ mais pas VS?

Cela faisait un moment que GCC ne m'avait pas attrapé avec celui-ci, mais c'est arrivé aujourd'hui. Mais je n'ai jamais compris pourquoi GCC nécessite un nom de type typedef dans les modèles, alors que VS et je suppose que ICC ne le font pas. La chose typedef typename est-elle un "bug" ou une norme trop stricte, ou quelque chose qui est laissé aux rédacteurs du compilateur?

Pour ceux qui ne savent pas ce que je veux dire, voici un échantillon:

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();
}

Le code ci-dessus se compile dans VS (et probablement dans ICC), mais échoue dans GCC car il le veut comme ceci:

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();
}

Remarque: Ce n'est pas une fonction réelle que j'utilise, mais juste quelque chose de stupide qui illustre le problème.

50
Robert Gould

Le nom de type est requis par la norme. La compilation de modèles nécessite une vérification en deux étapes. Lors de la première passe, le compilateur doit vérifier la syntaxe du modèle sans fournir réellement les substitutions de type. Dans cette étape, std :: map :: iterator est supposé être une valeur. S'il désigne un type, le mot-clé nom de type est requis.

Pourquoi est-ce nécessaire? Avant de substituer les types KEY et VALUE réels, le compilateur ne peut garantir que le modèle n'est pas spécialisé et que la spécialisation ne redéfinit pas le mot clé itérateur comme autre chose.

Vous pouvez le vérifier avec ce code:

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
}

Le dernier appel échoue avec une erreur indiquant que lors de la première étape de compilation du modèle f() Test :: value a été interprétée comme une valeur mais l'instanciation du modèle Test <> avec le type X donne un type.

Eh bien, GCC ne fait pas nécessite le typedef - typename est suffisant. Cela marche:

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

Ceci est un exemple d'un problème d'analyse sensible au contexte. La signification de la ligne en question n'apparaît pas uniquement dans la syntaxe de cette fonction - vous devez savoir si std::map<KEY,VALUE>::const_iterator est un type ou non.

Maintenant, je n'arrive pas à penser à un exemple de ce que ...::const_iterator pourrait être sauf un type, ce ne serait pas non plus une erreur. Donc je suppose que le compilateur peut découvrir qu'il a pour être un type, mais cela pourrait être difficile pour le pauvre compilateur (écrivains).

La norme requiert l'utilisation de typename ici, selon litb par la section 14.6/3 de la norme.

32
Magnus Hoff

Il semble que VS/ICC fournit le mot clé typename partout où il pense qu'il est requis. Notez que c'est une mauvaise chose (TM) - pour laisser le compilateur décider ce que vous voulez. Cela complique davantage le problème en inculquant la mauvaise habitude de sauter le typename lorsque cela est nécessaire et est un cauchemar de portabilité. Ce n'est certainement pas le comportement standard. Essayez en mode standard strict ou Comeau.

4
dirkgently

Il s'agit d'un bogue dans le compilateur Microsoft C++ - dans votre exemple, std :: map :: iterator peut ne pas être un type (vous pourriez avoir spécialisé std :: map sur KEY, VALUE pour que std :: map :: iterator soit un variable par exemple).

GCC vous oblige à écrire le code correct (même si ce que vous vouliez dire était évident), tandis que le compilateur Microsoft devine correctement ce que vous vouliez dire (même si le code que vous avez écrit était incorrect).

3
JoeG

Il convient de noter que le problème de tri valeur/type n'est pas le problème fondamental. Le problème principal est analyse. Considérer

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

Il n'y a aucun moyen de savoir s'il s'agit d'un cast ou d'un appel de fonction à moins que le mot clé typename soit obligatoire. Dans ce cas, le code ci-dessus contient un appel de fonction. En général, le choix ne peut pas être retardé sans renoncer complètement à l'analyse, il suffit de considérer le fragment

(a)(b)(c)

Dans le cas où vous ne vous en souvenez pas, le cast a une priorité plus élevée que l'appel de fonction en C, une des raisons pour lesquelles Bjarne voulait des conversions de style de fonction. Il n'est donc pas possible de dire si ce qui précède signifie

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

ou

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

ou

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

où j'ai inséré un espace pour indiquer le regroupement.

Notez également que le mot clé "templatename" est requis pour la même raison que "typename", vous ne pouvez pas analyser les choses sans connaître leur type en C/C++.

2
Yttrill