Officiellement, à quoi sert typename?

131

À l'occasion, j'ai vu des messages d'erreur vraiment indéchiffrables crachés gcclors 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 disparaissaient comme par magie en préfixant le typenamemot - clé au début du déclaration ... (Par exemple, la semaine dernière, je déclarais deux itérateurs comme membres d'une autre classe basée sur un modèle et je devais le faire) ...

Quelle est l'histoire typename?

dicroce
la source

Réponses:

207

Voici la citation du livre de Josuttis:

Le mot-clé a typenameé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, typenameest utilisé pour clarifier qu'il SubTypes'agit d'un type de class T. Ainsi, ptrest un pointeur vers le type T::SubType. Sans typename, SubType serait considéré comme un membre statique. Donc

T::SubType * ptr

serait une multiplication de valeur SubTypede type Tavec ptr.

Naveen
la source
2
Grand livre. Lisez-le une fois puis conservez-le comme référence si vous le souhaitez.
deft_code
1
Le lecteur avisé se rendra compte qu'une expression de multiplication n'est pas autorisée par la grammaire pour une déclaration de membre. En tant que tel, C ++ 20 supprime la nécessité de cela typename(mais pas tous!).
Davis Herring
Ne m'a pas convaincu. Une fois le modèle instancié, il est très bien défini quel est le sous
kovarex
36

Le post 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 interrompre 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 un 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 …
     // …
};

la grammaire du langage est mal interprétée T::A *aObj;comme une expression arithmétique, donc un nouveau mot-clé est introduit appelétypename

typename T::A* a6;

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

Puisque le mot-clé figurait sur la liste de paie, pourquoi ne pas corriger la confusion causée par la décision initiale de réutiliser le mot-clé de classe.

C'est pourquoi nous avons les deux

Vous pouvez jeter un œil à ce post , il vous aidera certainement, j'en ai juste extrait autant que je pourrais

Xinus
la source
Oui, mais alors pourquoi un nouveau mot-clé était-il typenamenécessaire, si vous pouviez utiliser le mot-clé existant classdans le même but?
Jesper
5
@Jesper: Je pense que la réponse de Xenus est déroutante ici. typenameest devenu nécessaire pour résoudre le problème d'analyse tel que décrit dans la réponse de Naveen en citant Josuttis. (Je ne pense pas que l'insertion d'un classà cet endroit aurait fonctionné.) Ce n'est qu'après que le nouveau mot-clé a été accepté pour ce cas, il a également été autorisé dans les déclarations d'argument de modèle ( ou est-ce que c'est des définitions? ), Car classil y a toujours eu un peu trompeur.
sbi
13

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 :: Sometype finira par faire référence à un nom de type ou à un membre statique de T. Donc, on utilise typenamepour le dire:

template<class T> somefunction( T * arg )
{
    typename T::sometype x; // works!
    .
    .
l'ombre de la lune
la source
6

Dans certaines situations où vous faites référence à un membre de soi-disant dépendant type (signifiant "dépendant du paramètre du modèle"), le compilateur ne peut pas toujours déduire sans ambiguïté la signification sémantique de la construction résultante, car il ne sait pas quel type de nom c'est (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 nom d'autre chose). Dans de tels cas, vous devez lever l'ambiguïté de la situation en indiquant 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é est typenamené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 la syntaxe correctement).

Bien sûr, un autre rôle du mot typename- clé est d'être utilisé dans les déclarations de paramètres de modèle.

Fourmi
la source
Voir également A Description du mot clé typename C ++ pour plus d'informations (d'arrière-plan).
Atafar le
5

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 l'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 referencetype est complètement différent de celui des autres Ts. Certes, cela ne change pas le genre de referencetype à quelque chose de différent, mais cela peut néanmoins arriver.

Maintenant, que se passe-t-il si vous écrivez vos propres modèles en utilisant ce testmodèle. Quelque chose comme ça

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

cela semble vous convenir car vous vous attendez à ce que ce test<T>::ptrsoit un type. Mais le compilateur ne sait pas et en fait il est même conseillé par la norme d'attendre le contraire, ce test<T>::ptrn'est pas un type. Pour dire au compilateur ce que vous attendez, vous devez ajouter un typenameavant. Le modèle correct ressemble à ceci

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

Bottom line: Vous devez ajouter typenameavant 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.)

phlipsie
la source
5

Deux utilisations:

  1. En tant que templatemot clé d'argument (au lieu de class)
  2. Un typenamemot clé indique au compilateur qu'un identificateur est un type (plutôt qu'une variable membre statique)
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}
Phillip Ngan
la source
4

Je pense que toutes les réponses ont mentionné que le typenamemot - clé est utilisé dans deux cas différents:

a) Lors de la déclaration d'un paramètre de type de modèle. par exemple

template<class T> class MyClass{};        // these two cases are
template<typename T> class MyNewClass{};  // exactly the same.

Qui il n'y a aucune différence entre eux et ils sont exactement les mêmes.

b) Avant d'utiliser un nom de type dépendant imbriqué pour un modèle.

template<class T>
void foo(const T & param)
{
   typename T::NestedType * value; // we should use typename here
}

Le fait de ne pas utiliser typenameentraîne des erreurs d'analyse / compilation.

Ce que je veux ajouter au deuxième cas, comme mentionné dans le livre de Scot Meyers Effective C ++ , c'est qu'il existe une exception à l'utilisation typenameavant un nom de type dépendant imbriqué . L'exception est que si vous utilisez le nom de type dépendant imbriqué comme classe de base ou dans une liste d'initialisation de membre , vous ne devez pas l'utiliser typename:

template<class T>
class D : public B<T>::NestedType               // No need for typename here
{
public:
   D(std::string str) : B<T>::NestedType(str)   // No need for typename here
   {
      typename B<T>::AnotherNestedType * x;     // typename is needed here
   }
}

Remarque: l' utilisation typenamepour le deuxième cas (c'est-à-dire avant le nom de type dépendant imbriqué) n'est pas nécessaire depuis C ++ 20.

Gupta
la source
2
#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;
}
Jobin
la source