1068 votes

Où et pourquoi dois-je mettre le "modèle" et "typename" de mots-clés?

Dans les modèles, où et pourquoi dois-je mettre en typename et template sur dépendante des noms? Qu'est-ce exactement sont dépendants des noms de toute façon? J'ai le code suivant:

template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        // Q: where to add typename/template here?
        typedef Tail::inUnion<U> dummy; 
    };
    template< > struct inUnion<T> {
    };
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
    // ...
    template<typename U> struct inUnion {
        char fail[ -sizeof(U) ]; // Cannot be instantiated for any U
    };
    template< > struct inUnion<T> {
    };
};

Le problème que j'ai est dans l' typedef Tail::inUnion<U> dummy ligne de. Je suis assez certain qu' inUnion est une personne à charge nom et VC++ est tout à fait juste en étouffer. Je sais aussi que je devrais être en mesure d'ajouter template quelque part pour indiquer au compilateur que inUnion est un modèle-id. Mais où exactement? Et faut-il alors supposer que inUnion est un modèle de classe, c'est à dire inUnion<U> le nom d'un type et non pas une fonction?

133voto

C++11

Problème

Bien que les règles en C++03 sujet de quand vous avez besoin d' typename et template sont en grande partie raisonnable, il y a un inconvénient gênant de sa formulation

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

Comme vous pouvez le voir, nous avons besoin de la désambiguïsation mot-clé, même si le compilateur peut parfaitement comprendre lui-même qu' A::result_type ne peuvent être int (et est donc un type), et de l' this->g ne peut être que le membre modèle g déclaré plus tard (même si A est explicitement spécialisés quelque part, cela ne modifie pas le code au sein de ce modèle, de sorte que son sens ne peut pas être affectée par un plus tard, la spécialisation de l' A!).

Courant de l'instanciation

Pour améliorer la situation, en C++11 la langue des pistes quand un type se réfère à l'enfermant modèle. À savoir que, le type doit avoir été formé en utilisant une certaine forme de nom, qui est son propre nom (ci-dessus, A, A<T>, ::A<T>). Un type référencé par un nom qui est connu pour être le courant de l'instanciation. Il peut y avoir plusieurs types qui sont tous au courant de l'instanciation si le type de qui le nom est formé, est un membre/classe imbriquée (puis, A::NestedClass et A sont à la fois actuel instanciations).

Basé sur cette notion, la langue dit qu' CurrentInstantiation::Foo, Foo et CurrentInstantiationTyped->Foo (comme A *a = this; a->Foo) sont tous membres de l'actuel instanciation si elles sont membres d'une classe qui est l'actuel instanciation ou de l'une de ses non-dépendante des classes de base (il vous suffit de faire la recherche par nom immédiatement).

Les mots clés typename et template sont maintenant pas plus requis si le qualificatif est un membre de l'actuel de l'instanciation. Un point crucial de rappeler ici est que l' A<T> est encore un de dépendant du type de nom (après tout, T est également dépendante du type). Mais A<T>::result_type est connu pour être de type, le compilateur "comme par magie" regarde ce genre de types dépendants.

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

C'est impressionnant, mais que pouvons-nous faire mieux? La langue va même plus loin et exige qu'une mise en œuvre regarde à nouveau jusqu' D::result_type lors de l'instanciation D::f (même si il a trouvé son sens déjà à la définition du temps). Lorsque maintenant le résultat de recherche diffère ou les résultats dans l'ambiguïté, le programme est mal formé et un diagnostic doit être donné. Imaginez ce qui arriverait si nous avons défini C comme ceci

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

Un compilateur est nécessaire à la capture de l'erreur lors de l'instanciation D<int>::f. Ainsi, vous obtenez le meilleur des deux mondes: "le Retard" recherche de vous protéger si vous pourriez avoir de la difficulté à charge des classes de base, et aussi "Immédiate" de recherche qui vous libère typename et template.

Inconnu spécialisations

Dans le code de l' D, le nom de l' typename D::questionable_type n'est pas un membre de l'actuel de l'instanciation. Au lieu de la langue de la marque comme un membre d'une inconnue de spécialisation. En particulier, ce qui est toujours le cas lorsque vous effectuez DependentTypeName::Foo ou DependentTypedName->Foo et soit le type de charge n'est pas au courant de l'instanciation (dans ce cas, le compilateur peut faire et de dire "nous allons examiner plus tard ce qu' Foo ) ou il est le courant de l'instanciation et le nom n'a pas été trouvé ou ses non-dépendante des classes de base et il y a aussi dépendante des classes de base.

Imaginez ce qui arriverait si nous avions une fonction membre h dans le ci-dessus définis A modèle de classe

void h() {
  typename A<T>::questionable_type x;
}

En C++03, la langue a permis de rattraper cette erreur, car il ne pourrait jamais être un moyen valable pour instancier A<T>::h (quel que soit l'argument que vous donnez à T). En C++11, la langue a maintenant un chèque supplémentaire de donner une raison de plus pour les compilateurs pour mettre en œuvre cette règle. Depuis A a la charge des classes de base, et A déclare aucun membre de l' questionable_type, le nom de l' A<T>::questionable_type est ni un membre de l'actuel de l'instanciation , ni un membre d'un inconnu spécialisation. Dans ce cas, il devrait y avoir aucun moyen que ce code pourrait valablement compiler à l'instanciation de temps, de sorte que la langue interdit à un nom de où le qualificatif est un courant de l'instanciation de n'être ni un membre d'un inconnu spécialisation, ni membre de l'actuel de l'instanciation (toutefois, cette violation est toujours pas nécessaire d'être diagnostiqué).

Les exemples et les anecdotes

Vous pouvez essayer cette connaissance de cette réponse et de voir si les définitions ci-dessus ont un sens pour vous sur un exemple réel (ils se répètent un peu moins détaillée dans la réponse).

Le C++11, les règles, les valide en C++03 code mal formé (qui n'était pas prévue par le comité C++, mais ne sera probablement pas fixe)

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

Cela valide en C++03 code lier this->f de A::f lors de l'instanciation de temps et tout va bien. C++11 est cependant immédiatement le lie à B::f et nécessite un double-vérifier lors de l'instanciation, de vérifier si le recherche encore des matches. Cependant lors de l'instanciation C<A>::g, la Domination de la Règle s'applique et la recherche d'trouverez A::f à la place.

93voto

Filip Roséen - refp Points 24995

PRÉFACE

Ce post est destiné à être un facile-à-lire alternative à litb post.

L'objectif sous-jacent est le même; une explication à la question "Quand?" et "Pourquoi?" typename et template doit être appliqué.


Quel est le but de l' typename et template?

typename et template sont utilisables dans d'autres situations que lors de la déclaration d'un modèle.

Dans certains contextes, et en C++ le compilateur doit être explicitement dit comment traiter un nom, et tous ces contextes ont une chose en commun; ils dépendent d'au moins un modèle à paramètre.

Nous nous référons à ces noms, où il peut y avoir une ambiguïté dans l'interprétation, comme; "dépendant de noms".

Ce poste va offrir une explication de la relation entre dépendante-noms, et les deux mots-clés.


UN EXTRAIT EN DIT PLUS QUE 1000 MOTS

Essayer d'expliquer ce qui se passe dans la suite de la fonction de modèle, soit à vous-même, un ami, ou peut-être de votre chat, ce qui se passe dans l'énoncé marqué (Un)?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


Il pourrait ne pas être aussi facile qu'on le pense, plus précisément le résultat de l'évaluation (Un) fortement repose sur la définition du type passé en tant que modèle à paramètre T.

Différents Ts peuvent changer radicalement la sémantique impliqués.

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();




Les deux scénarios différents:

  • Si nous instancier la fonction de modèle de type X, comme dans (C), nous aurons une déclaration d'un pointeur vers int nommé x, mais;

  • si nous instancier le modèle de type Y, comme en (D), (Un) serait plutôt consister en une expression qui calcule le produit de 123 multiplié avec certains déjà déclaré la variable x.



LA JUSTIFICATION

La Norme C++ se soucie de la sécurité et le bien-être, au moins dans ce cas.

Pour éviter une mise en œuvre à partir de potentiellement souffrant de mauvaises surprises, la Norme mandats que nous avons en quelque sorte l'ambiguïté d'un dépendante de nom par explicitement indiquant l'intention de n'importe où nous aimerions traiter le nom soit comme un type de nom, ou d'un modèle-id.

Si rien n'est indiqué, les dépendants de nom sera considéré comme une variable ou une fonction.



COMMENT GÉRER DÉPENDANTE DES NOMS?

Si c'était un film d'Hollywood, dépendant de noms serait la maladie qui se propage par le contact du corps, instantanément affecte son hôte pour le rendre confus. La Confusion qui pourrait, éventuellement, conduire à un mal-formé perso-, heu.. programme.

Un de dépendant nom est tout nom qui, directement ou indirectement, dépend d'un modèle à paramètre.

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

Nous avons quatre dépendant de noms dans l'extrait ci-dessus:

  • E)
    • "type" dépend de l'instanciation d' SomeTrait<T>, qui comprennent T,, et;
  • F)
    • "NestedTrait", qui est un modèle-id, dépend SomeTrait<T>,, et;
    • "type" à la fin (F) dépend NestedTrait, qui dépend de l' SomeTrait<T>,, et;
  • G)
    • "données", qui ressemble à un membre de la fonction de modèle, est indirectement un dépendante de nom puisque le type de foo dépend de l'instanciation d' SomeTrait<T>.

Ni l'un ni l'énoncé (E), (F) ou (G) est valide si le compilateur pourrait interpréter le dépendantes-les noms de variables/fonctions (comme indiqué plus haut est ce qui se passe si nous n'avons pas explicitement dire le contraire).

LA SOLUTION

Pour faire g_tmpl ont une définition valable, nous devons indiquer explicitement au compilateur que nous nous attendons à un type dans (E), un modèle-id et un type (F), et un modèle-id (G).

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

Chaque fois qu'un nom désigne un type, tous les noms doivent être soit de type-noms ou des espaces de noms, avec cela à l'esprit, il est assez facile de voir que l'on applique typename au début de notre nom qualifié.

template cependant, est différente à cet égard, car il n'y a aucun moyen d'en venir à une conclusion telle que: "oh, c'est un modèle, que cette autre chose doit être aussi un modèle". Cela signifie que l'on applique template directement en face de n'importe quel nom que nous aimerions traiter comme tel.



PUIS-JE COLLER LES MOTS-CLÉS EN FACE DE SON NOM?

"Je ne peux tout simplement bâton typename et template ) devant le nom? Je ne veux pas à vous soucier du contexte dans lequel ils apparaissent..." - Some C++ Developer

Les règles de la Norme stipule que vous pouvez appliquer des mots-clés comme longtemps que vous êtes face à un qualifié-nom (K), mais si le nom n'est pas qualifié, l'application est mal formé (L).

namespace N {
  template<class T>
  struct X { };
}

         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

Remarque: l'Application d' typename ou template dans un contexte où il n'est pas nécessaire n'est pas considéré comme une bonne pratique; juste parce que vous pouvez faire quelque chose, ne signifie pas que vous devriez.




En outre il y a des contextes où l' typename et template sont explicitement interdits:

  • Lors de la spécification des bases dont la classe hérite

    Chaque nom est inscrit dans une classe dérivée de la base de spécificateur de liste est déjà traité comme un type de nom, spécifiant explicitement typename est à la fois mal formé, et redondants.

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };
    


  • Lorsque le modèle-id est un être mentionnées dans une classe dérivée à l'aide de la directive

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    

20voto

Luc Touraille Points 29252
typedef typename Tail::inUnion<U> dummy;

Cependant, je ne suis pas sûr que vous êtes à la mise en œuvre de inUnion est correct. Si je comprends bien, cette classe n'est pas censé être instancié, donc le "fail" de l'onglet ne sera jamais avtually échoue. Il serait peut-être mieux indique si le type est dans l'union ou pas avec une simple valeur booléenne.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS: jetez un oeil à Boost::Variant

PS2: jetez un oeil à typelists, notamment dans Andrei Alexandrescu du livre: Modern C++ Design

20voto

Rapptz Points 10135

Cette réponse est destinée à être assez court et doux une réponse (une partie de) l'intitulé de la question. Si vous voulez une réponse avec plus de détails qui explique pourquoi vous devez les mettre là, s'il vous plaît aller ici.


La règle générale pour mettre la typename mot-clé est la plupart du temps lorsque vous utilisez un paramètre de modèle et que vous souhaitez accéder à un imbriquée typedef ou aide-alias, par exemple:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

Notez que ceci s'applique également aux fonctions de meta ou les choses qui prennent modèle générique de paramètres. Cependant, si le paramètre de modèle fourni est de type explicite, alors vous n'avez pas à spécifier typename, par exemple:

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

Les règles générales pour mettre la template mot-clé sont pour la plupart les mêmes, sauf qu'ils impliquent généralement des fonctions de membre ou de membres statiques qui sont basés sur des modèles avec la structure/classe étant lui-même basé sur un modèle, par exemple:

Compte tenu de cette structure et la fonction:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

Tenter d'accéder t.get<int>() à partir de l'intérieur de la fonction génère une erreur:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

Ainsi, dans ce contexte, vous aurez besoin de l' template mot-clé à l'avance et de l'appeler de la sorte:

t.template get<int>()

De cette façon, le compilateur va analyser correctement, plutôt que d' t.get < int.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X