web-dev-qa-db-fra.com

Officiellement, à quoi sert le nom de type?

À l'occasion, j'ai vu des messages d'erreur vraiment indéchiffrables crachés par gcc lors de l'utilisation de modèles ... Plus précisément, j'ai eu des problèmes où des déclarations apparemment correctes provoquaient des erreurs de compilation très étranges qui ont disparu comme par magie en préfixant le mot-clé "typename" à le début de la déclaration ... (Par exemple, la semaine dernière, je déclarais deux itérateurs membres d'une autre classe basée sur des modèles et je devais le faire) ...

Quelle est l'histoire du nom de type?

113
dicroce

Voici la citation du livre de Josuttis:

Le mot clé typename a été introduit pour spécifier que l'identifiant qui suit est un type. Prenons l'exemple suivant:

template <class T>
Class MyClass
{
  typename T::SubType * ptr;
  ...
};

Ici, typename est utilisé pour clarifier que SubType est un type de classe T. Ainsi, ptr est un pointeur vers le type T :: SubType. Sans nom de type, SubType serait considéré comme un membre statique. Ainsi

T::SubType * ptr

serait une multiplication de la valeur SubType de type T avec ptr.

180
Naveen

Message BLog de Stan Lippman suggère: -

Stroustrup a réutilisé le mot-clé de classe existant pour spécifier un paramètre de type plutôt que d'introduire un nouveau mot-clé qui pourrait bien sûr casser les programmes existants. Ce n'est pas qu'un nouveau mot clé n'a pas été pris en compte, mais simplement qu'il n'a pas été jugé nécessaire compte tenu de sa perturbation potentielle. Et jusqu'à la norme ISO-C++, c'était la seule façon de déclarer un paramètre de type.

Donc, fondamentalement, Stroustrup a réutilisé le mot-clé de classe sans introduire de nouveau mot-clé qui est modifié par la suite dans la norme pour les raisons suivantes

Comme l'exemple donné

template <class T>
class Demonstration {
public:
void method() {
    T::A *aObj; // oops …
     // …
};

grammaire linguistique mal interprétée T::A *aObj; en tant qu'expression arithmétique, un nouveau mot clé est donc appelé typename

typename T::A* a6;

il demande au compilateur de traiter l'instruction suivante comme une déclaration.

Puisque le mot-clé était sur la paie, diable, pourquoi ne pas corriger la confusion causée par la décision d'origine de réutiliser le mot-clé de classe.

C'est pourquoi nous avons les deux

Vous pouvez jeter un oeil à ce post , cela vous aidera certainement, je viens d'en extraire autant que possible

34
Xinus

Considérez le code

template<class T> somefunction( T * arg )
{
    T::sometype x; // broken
    .
    .

Malheureusement, le compilateur n'est pas obligé d'être psychique, et ne sait pas si T :: someype finira par se référer à un nom de type ou à un membre statique de T. Donc, on utilise typename pour le dire:

template<class T> somefunction( T * arg )
{
    typename T::sometype x; // works!
    .
    .
13
moonshadow

Dans certaines situations où vous faites référence à un membre du type dit dépendant (signifiant "dépendant du paramètre de modèle"), le compilateur ne peut pas toujours déduire sans ambiguïté la signification sémantique de la construction résultante, car elle ne le fait pas. savoir de quel type de nom il s'agit (c'est-à-dire s'il s'agit d'un nom d'un type, d'un nom d'un membre de données ou d'un autre nom). Dans de tels cas, vous devez lever l'ambiguïté en disant explicitement au compilateur que le nom appartient à un nom de type défini comme membre de ce type dépendant.

Par exemple

template <class T> struct S {
  typename T::type i;
};

Dans cet exemple, le mot clé typename est nécessaire à la compilation du code.

La même chose se produit lorsque vous souhaitez faire référence à un membre de modèle de type dépendant, c'est-à-dire à un nom qui désigne un modèle. Vous devez également aider le compilateur en utilisant le mot clé template, bien qu'il soit placé différemment

template <class T> struct S {
  T::template ptr<int> p;
};

Dans certains cas, il peut être nécessaire d'utiliser les deux

template <class T> struct S {
  typename T::template ptr<int>::type i;
};

(si j'ai obtenu la syntaxe correctement).

Bien entendu, un autre rôle du mot clé typename doit être utilisé dans les déclarations de paramètres de modèle.

6
AnT

Le secret réside dans le fait qu'un modèle peut être spécialisé pour certains types. Cela signifie qu'il peut également définir une interface complètement différente pour plusieurs types. Par exemple, vous pouvez écrire:

template<typename T>
struct test {
    typedef T* ptr;
};

template<>         // complete specialization 
struct test<int> { // for the case T is int
    T* ptr;
};

On pourrait se demander pourquoi est-ce utile et en effet: cela semble vraiment inutile. Mais gardez à l'esprit que par exemple std::vector<bool> le type reference est complètement différent de celui des autres Ts. Certes, cela ne change pas le type de reference d'un type en quelque chose de différent, mais cela pourrait néanmoins arriver.

Maintenant, que se passe-t-il si vous écrivez vos propres modèles à l'aide de ce modèle test. Quelque chose comme ça

template<typename T>
void print(T& x) {
    test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

il semble que ça va pour vous parce que vous attendre cette test<T>::ptr est un type. Mais le compilateur ne sait pas et en fait il est même conseillé par la norme d'attendre le contraire, test<T>::ptr n'est pas un type. Pour dire au compilateur ce que vous attendez, vous devez ajouter un typename avant. Le modèle correct ressemble à ceci

template<typename T>
void print(T& x) {
    typename test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

Conclusion: vous devez ajouter typename avant chaque fois que vous utilisez un type imbriqué de modèle dans vos modèles. (Bien sûr, uniquement si un paramètre de modèle de votre modèle est utilisé pour ce modèle interne.)

5
phlipsy

Deux usages:

  1. En tant que mot-clé d'argument modèle (au lieu de "classe")
  2. Un mot clé typename indique au compilateur qu'un identifiant est un type (plutôt qu'une variable membre statique)
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}
4
Phillip Ngan
#include <iostream>

class A {
public:
    typedef int my_t;
};

template <class T>
class B {
public:
    // T::my_t *ptr; // It will produce compilation error
    typename T::my_t *ptr; // It will output 5
};

int main() {
    B<A> b;
    int my_int = 5;
    b.ptr = &my_int;
    std::cout << *b.ptr;
    std::cin.ignore();
    return 0;
}
2
Jobin