Frage Wo und warum muss ich die Schlüsselwörter "template" und "typename" eingeben?


In Vorlagen, wo und warum muss ich setzen typename und template auf abhängige Namen? Was genau sind abhängige Namen überhaupt? Ich habe den folgenden Code:

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[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
    };
    template< > struct inUnion<T> {
    };
};

Das Problem, das ich habe, ist in der typedef Tail::inUnion<U> dummy Linie. Ich bin ziemlich sicher, dass inUnion ist ein abhängiger Name, und VC ++ hat recht, wenn er daran erstickt. Ich weiß auch, dass ich hinzufügen könnte template irgendwo um dem Compiler mitzuteilen, dass inUnion eine Template-ID ist. Aber wo genau? Und sollte es dann annehmen, dass inUnion eine Klassenvorlage ist, d.h. inUnion<U> Benennt einen Typ und keine Funktion?


901
2018-03-04 11:56


Ursprung


Antworten:


Um ein C ++ - Programm zu parsen, muss der Compiler wissen, ob bestimmte Namen Typen sind oder nicht. Das folgende Beispiel zeigt Folgendes:

t * f;

Wie soll das geparst werden? Für viele Sprachen muss ein Compiler die Bedeutung eines Namens nicht kennen, um zu analysieren und im Grunde zu wissen, welche Aktion eine Codezeile ausführt. In C ++ kann das oben genannte jedoch je nach Bedeutung sehr unterschiedliche Interpretationen ergeben t meint. Wenn es ein Typ ist, dann wird es eine Deklaration eines Zeigers sein f. Wenn es jedoch kein Typ ist, wird es eine Multiplikation sein. So heißt es im C ++ Standard in Absatz (3/7):

Einige Namen bezeichnen Typen oder Vorlagen. Wenn ein Name gefunden wird, muss im Allgemeinen festgestellt werden, ob dieser Name eine dieser Entitäten kennzeichnet, bevor das Programm, das ihn enthält, weiter analysiert wird. Der Prozess, der dies festlegt, wird Namenssuche genannt.

Wie wird der Compiler herausfinden, was ein Name ist t::x bezieht sich auf, wenn t bezieht sich auf einen Template-Typ Parameter? x könnte ein statisches int-Datenelement sein, das multipliziert werden könnte oder genauso gut eine geschachtelte Klasse oder typedef sein könnte, die zu einer Deklaration führen könnte. Wenn ein Name diese Eigenschaft hat - dass er nicht gefunden werden kann, bis die tatsächlichen Template-Argumente bekannt sind - dann heißt er a abhängiger Name (es "hängt" von den Vorlagenparametern ab).

Es empfiehlt sich, einfach zu warten, bis der Benutzer die Vorlage instanziiert:

Warten wir, bis der Benutzer die Vorlage instanziiert und später die wahre Bedeutung von t::x * f;. 

Dies wird funktionieren und wird vom Standard als möglicher Implementierungsansatz zugelassen. Diese Compiler kopieren im Grunde den Text der Vorlage in einen internen Puffer, und nur wenn eine Instanziierung benötigt wird, parsen sie die Vorlage und erkennen möglicherweise Fehler in der Definition. Aber anstatt die Benutzer der Vorlage zu belästigen (schlechte Kollegen!) Mit Fehlern, die der Autor einer Vorlage gemacht hat, wählen andere Implementierungen, Vorlagen früh zu überprüfen und Fehler in der Definition so schnell wie möglich zu geben, bevor eine Instanziierung überhaupt stattfindet.

Es muss also eine Möglichkeit geben, dem Compiler mitzuteilen, dass bestimmte Namen Typen sind und dass bestimmte Namen keine sind.

Das Schlüsselwort "type"

Die Antwort ist: Wir Entscheiden Sie, wie der Compiler dies analysieren soll. Ob t::x ist ein abhängiger Name, dann müssen wir ihn voranstellen typenamedem Compiler mitzuteilen, dass er es auf eine bestimmte Art analysieren soll. Der Standard sagt um (14.6 / 2):

Ein Name, der in einer Schablonendeklaration oder -definition verwendet wird und der von einem Schablonenparameter abhängt   Es wird angenommen, dass ein Typ nicht benannt wird, es sei denn, die Suche nach anwendbaren Namen findet einen Typnamen oder der Name ist qualifiziert   nach dem Schlüsselwort typename.

Es gibt viele Namen dafür typename ist nicht notwendig, weil der Compiler mit dem passenden Namen-Lookup in der Template-Definition herausfinden kann, wie man ein Konstrukt selbst analysiert - zum Beispiel mit T *f;, wann T ist ein Typvorlagenparameter. Aber für t::x * f; Um eine Erklärung zu sein, muss es als geschrieben werden typename t::x *f;. Wenn Sie das Schlüsselwort weglassen und der Name als Nicht-Typ angenommen wird, aber wenn die Instanziierung einen Typ erkennt, werden vom Compiler die üblichen Fehlermeldungen ausgegeben. Manchmal wird der Fehler folglich zur Definitionszeit gegeben:

// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;

Die Syntax erlaubt typename nur vor qualifizierten Namen - Es gilt daher als selbstverständlich, dass unqualifizierte Namen sich immer auf Typen beziehen, wenn sie dies tun.

Ein ähnliches Problem besteht für Namen, die Vorlagen bezeichnen, wie im Einführungstext angedeutet.

Das Schlüsselwort "Vorlage"

Merken Sie sich das obige Zitat und wie verlangt der Standard auch eine spezielle Handhabung für Vorlagen? Nehmen wir das folgende unschuldig aussehende Beispiel:

boost::function< int() > f;

Es könnte für einen menschlichen Leser offensichtlich sein. Nicht so für den Compiler. Stellen Sie sich die folgende willkürliche Definition vor boost::function und f:

namespace boost { int function = 0; }
int main() { 
  int f = 0;
  boost::function< int() > f; 
}

Das ist tatsächlich eine gültige Ausdruck! Es verwendet den Kleiner-als-Operator zum Vergleichen boost::function gegen Null (int()) und verwendet dann den Größer-als-Operator, um das Ergebnis zu vergleichen bool gegen f. Wie Sie vielleicht wissen, boost::function  im echten Leben ist eine Vorlage, also weiß der Compiler (14.2 / 3):

Nach der Namenssuche (3.4) wird festgestellt, dass ein Name ein Vorlagenname ist, wenn auf diesen Namen ein <, das <folgt   immer als Anfang einer Template-Argument-Liste und niemals als Name gefolgt von Less-Than   Operator.

Jetzt sind wir wieder beim selben Problem wie bei typename. Was, wenn wir noch nicht wissen können, ob der Name eine Vorlage ist, wenn der Code analysiert wird? Wir müssen einfügen template unmittelbar vor dem Vorlagennamen, wie von 14.2/4. Das sieht so aus:

t::template f<int>(); // call a function template

Vorlagennamen können nicht nur nach a auftreten :: aber auch nach einem -> oder . in einem Klassenmitgliedszugriff. Sie müssen das Schlüsselwort dort auch einfügen:

this->template f<int>(); // call a function template

Abhängigkeiten

Für die Leute, die dicke Standardische Bücher in ihrem Regal haben und wissen wollen, wovon genau ich rede, werde ich ein wenig darüber sprechen, wie dies im Standard spezifiziert ist.

In Schablonendeklarationen haben einige Konstrukte unterschiedliche Bedeutungen, abhängig davon, welche Vorlagenargumente Sie verwenden, um die Vorlage zu instanziieren: Ausdrücke können unterschiedliche Typen oder Werte haben, Variablen können unterschiedliche Typen haben oder Funktionsaufrufe können verschiedene Funktionen aufrufen. Solche Konstrukte werden allgemein gesagt abhängen auf Vorlagenparameter.

Der Standard definiert genau die Regeln, ob ein Konstrukt abhängig ist oder nicht. Sie trennt sie in logisch unterschiedliche Gruppen: Man fängt Typen ein, andere fangen Ausdrücke ein. Ausdrücke können von ihrem Wert und / oder ihrem Typ abhängen. So haben wir, mit typischen Beispielen, angehängt:

  • Abhängige Typen (z. B. ein Typvorlagenparameter T)
  • Wertabhängige Ausdrücke (z. B. ein Nicht-Typ-Vorlagenparameter N)
  • Typabhängige Ausdrücke (z. B. eine Umwandlung in einen Typvorlagenparameter) (T)0)

Die meisten Regeln sind intuitiv und rekursiv aufgebaut: Beispielsweise ein Typ, der als konstruiert ist T[N] ist ein abhängiger Typ wenn N ist ein wertabhängiger Ausdruck oder T ist ein abhängiger Typ. Die Details dazu können im Abschnitt gelesen werden (14.6.2/1) für abhängige Typen, (14.6.2.2) für typabhängige Ausdrücke und (14.6.2.3) für wertabhängige Ausdrücke.

Abhängige Namen

Der Standard ist ein bisschen unklar was genau ist ein abhängiger Name. Auf einem einfachen Lesen (Sie wissen, das Prinzip der geringsten Überraschung), alles definiert es als a abhängiger Name ist der Spezialfall für die Funktionsnamen unten. Aber seit klar T::x muss auch im Instantiierungskontext nachgeschlagen werden, es muss auch ein abhängiger Name sein (glücklicherweise hat der Ausschuss ab Mitte C ++ 14 begonnen, zu untersuchen, wie diese verwirrende Definition behoben werden kann).

Um dieses Problem zu vermeiden, habe ich auf eine einfache Interpretation des Standardtextes zurückgegriffen. Von allen Konstrukten, die abhängige Typen oder Ausdrücke bezeichnen, repräsentiert eine Teilmenge von ihnen Namen. Diese Namen sind daher "abhängige Namen". Ein Name kann verschiedene Formen annehmen - der Standard sagt:

Ein Name ist eine Verwendung eines Identifikators (2.11), einer Operatorfunktions-ID (13.5), einer Konvertierungsfunktions-ID (12.3.2) oder einer Template-ID (14.2), die eine Entität oder ein Label (6.6.4, 6.1)

Ein Bezeichner ist nur eine einfache Folge von Zeichen / Ziffern, während die nächsten zwei sind operator + und operator type bilden. Die letzte Form ist template-name <argument list>. All dies sind Namen, und bei konventioneller Verwendung im Standard kann ein Name auch Qualifier enthalten, die angeben, in welchem ​​Namensraum oder Klasse ein Name gesucht werden soll.

Ein wertabhängiger Ausdruck 1 + N ist kein Name, aber N ist. Die Teilmenge aller abhängigen Konstrukte, die Namen sind, wird aufgerufen abhängiger Name. Funktionsnamen können jedoch unterschiedliche Bedeutung in verschiedenen Instanzen einer Vorlage haben, werden aber leider nicht von dieser allgemeinen Regel erfasst.

Abhängige Funktionsnamen

Nicht primär ein Anliegen dieses Artikels, aber noch erwähnenswert: Funktionsnamen sind eine Ausnahme, die getrennt behandelt werden. Ein Bezeichnerfunktionsname ist nicht von sich selbst abhängig, sondern von den typabhängigen Argumentausdrücken, die in einem Aufruf verwendet werden. Im Beispiel f((T)0), f ist ein abhängiger Name. Im Standard ist dies unter angegeben (14.6.2/1).

Zusätzliche Hinweise und Beispiele

In vielen Fällen brauchen wir beide typename und template. Ihr Code sollte wie folgt aussehen

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

Das Schlüsselwort template muss nicht immer im letzten Teil eines Namens erscheinen. Es kann in der Mitte vor einem Klassennamen erscheinen, der als Bereich verwendet wird, wie im folgenden Beispiel

typename t::template iterator<int>::value_type v;

In einigen Fällen sind die Schlüsselwörter wie unten beschrieben verboten

  • Auf den Namen einer abhängigen Basisklasse dürfen Sie nicht schreiben typename. Es wird angenommen, dass der Name ein Klassenname ist. Dies gilt für beide Namen in der Basisklassenliste und der Konstruktorinitialisierungsliste:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
    
  • In Verwendung-Deklarationen ist es nicht möglich zu verwenden template nach dem letzten ::und das C ++ Komitee sagte nicht an einer Lösung arbeiten.

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };
    

931
2018-03-05 00:27



C ++ 11

Problem

Während der Regeln in C ++ 03 wann man braucht typename und template sind weitgehend sinnvoll, es gibt einen ärgerlichen Nachteil seiner Formulierung

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

Wie man sehen kann, brauchen wir das Disambiguierungs-Schlüsselwort, selbst wenn der Compiler das selbst perfekt herausfinden könnte A::result_type kann nur sein int (und ist daher ein Typ), und this->g kann nur die Mitgliedervorlage sein g später erklärt (auch wenn A ist irgendwo explizit spezialisiert, das würde den Code innerhalb dieser Vorlage nicht beeinflussen, so dass seine Bedeutung von einer späteren Spezialisierung nicht beeinflusst werden kann A!).

Aktuelle Instanziierung

Um die Situation zu verbessern, wird in C ++ 11 die Sprache verfolgt, wenn sich ein Typ auf die umschließende Vorlage bezieht. Um das zu wissen, muss der Typ unter Verwendung einer bestimmten Form des Namens gebildet worden sein, der sein eigener Name ist (oben A, A<T>, ::A<T>). Ein Typ, auf den sich ein solcher Name bezieht, ist bekanntermaßen der aktuelle Instanziierung. Es kann mehrere Typen geben, die alle die aktuelle Instanz sind, wenn der Typ, aus dem der Name gebildet wird, ein Member / eine geschachtelte Klasse ist ( A::NestedClass und A sind beide aktuelle Instanziierungen).

Basierend auf dieser Vorstellung sagt die Sprache das CurrentInstantiation::Foo, Foo und CurrentInstantiationTyped->Foo (sowie A *a = this; a->Foo) sind alle Mitglied der aktuellen Instanziierung  ob Sie sind Mitglieder einer Klasse, bei der es sich um die aktuelle Instanziierung oder eine ihrer nicht abhängigen Basisklassen handelt (indem Sie die Namenssuche sofort ausführen).

Die Schlüsselwörter typename und template sind jetzt nicht mehr erforderlich, wenn das Qualifikationsmerkmal ein Mitglied der aktuellen Instanziierung ist. Ein wichtiger Punkt hier ist das zu erinnern A<T> ist immer noch ein typabhängiger Name (immerhin T ist auch typabhängig). Aber A<T>::result_type ist bekanntlich ein Typ - der Compiler wird "magisch" in diese Art abhängiger Typen schauen, um das herauszufinden.

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

Das ist beeindruckend, aber können wir es besser machen? Die Sprache geht sogar noch weiter und erfordert dass eine Implementierung erneut nachschlägt D::result_type beim Instanziieren D::f (auch wenn es bereits zur Definitionszeit seine Bedeutung gefunden hat). Wenn nun das Nachschlageergebnis abweicht oder zu Mehrdeutigkeiten führt, ist das Programm schlecht ausgebildet und es muss eine Diagnose gegeben werden. Stellen Sie sich vor, was passiert, wenn wir es definieren C so was

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

Ein Compiler ist erforderlich, um den Fehler beim Instanziieren abzufangen D<int>::f. So erhalten Sie das Beste aus den zwei Welten: "Delayed" Lookup schützt Sie, wenn Sie Probleme mit abhängigen Basisklassen bekommen könnten, und auch "Immediate" Lookup, das Sie befreit typename und template.

Unbekannte Spezialisierungen

Im Code von D, der Name typename D::questionable_type ist kein Mitglied der aktuellen Instanziierung. Stattdessen markiert die Sprache es als Mitglied einer unbekannten Spezialisierung. Dies ist insbesondere immer dann der Fall, wenn Sie es tun DependentTypeName::Foo oder DependentTypedName->Foo und entweder der abhängige Typ ist nicht die aktuelle Instantiierung (in diesem Fall kann der Compiler aufgeben und sagen "Wir werden später was schauen Foo ist) oder es ist Die aktuelle Instanziierung und der Name wurden weder in ihr noch in ihren nicht abhängigen Basisklassen gefunden, und es gibt auch abhängige Basisklassen.

Stellen Sie sich vor, was passiert, wenn wir eine Mitgliedsfunktion hätten h innerhalb des oben definierten A Klassenvorlage

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

In C ++ 03 konnte die Sprache diesen Fehler abfangen, da es niemals einen gültigen Weg zur Instanziierung geben konnte A<T>::h (Was auch immer Sie Argument geben T). In C ++ 11 hat die Sprache jetzt eine weitere Überprüfung, um den Compilern mehr Grund zu geben, diese Regel zu implementieren. Schon seit A hat keine abhängigen Basisklassen und A erklärt kein Mitglied questionable_type, der Name A<T>::questionable_type ist weder ein Mitglied der aktuellen Instanziierung Noch ein Mitglied einer unbekannten Spezialisierung. In diesem Fall sollte es nicht möglich sein, dass dieser Code zur Instanziierungszeit gültig kompiliert werden kann. Daher verbietet die Sprache einen Namen, bei dem das Qualifikationsmerkmal die aktuelle Instanz weder Mitglied einer unbekannten Spezialisierung noch Mitglied der aktuellen Instanz ist , diese Verletzung muss noch nicht diagnostiziert werden).

Beispiele und Wissenswertes

Sie können dieses Wissen anprobieren diese Antwort und sehen Sie, ob die obigen Definitionen für Sie in einem realen Beispiel sinnvoll sind (sie werden in dieser Antwort etwas weniger detailliert wiederholt).

Die C ++ 11-Regeln machen den folgenden gültigen C ++ 03-Code unpassend (was vom C ++ - Komitee nicht beabsichtigt war, aber wahrscheinlich nicht behoben wird)

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

Dieser gültige C ++ 03-Code würde binden this->f zu A::f zur Instanziierungszeit und alles ist in Ordnung. C ++ 11 bindet es jedoch sofort an B::f und erfordert eine Doppelprüfung beim Instanziieren, um zu überprüfen, ob die Suche noch übereinstimmt. Jedoch beim Instanziieren C<A>::g, das Vorherrschaftsregel gilt und suchen wird finden A::f stattdessen.


118
2017-07-10 20:02



VORWORT

Dieser Beitrag soll ein sein leicht zu lesen als Alternative litbs Beitrag.

Der zugrunde liegende Zweck ist der gleiche; eine Erklärung zu "Wann?" und warum?" typename und template muss angewendet werden.


Was ist der Zweck von typename und template?

typename und template sind in anderen Umständen als bei der Deklaration einer Vorlage verwendbar.

Es gibt bestimmte Kontexte in C ++ wobei dem Compiler explizit gesagt werden muss, wie ein Name zu behandeln ist, und alle diese Kontexte eins gemeinsam haben; sie hängen von mindestens einem ab Vorlagenparameter.

Wir beziehen uns auf solche Namen, bei denen es eine Mehrdeutigkeit bei der Interpretation geben kann; "abhängige Namen".

Dieser Beitrag bietet eine Erklärung für die Beziehung zwischen abhängige Namenund die zwei Schlüsselwörter.


Ein SNIPPET sagt mehr als 1000 Worte

Versuchen Sie zu erklären, was im Folgenden passiert Funktions-Vorlageentweder zu dir selbst, zu einem Freund oder vielleicht zu deiner Katze; was passiert in der Erklärung markiert (EIN)

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


Es ist vielleicht nicht so einfach wie man denkt, genauer gesagt, das Ergebnis der Bewertung (EIN) schwer hängt davon ab über die Definition des als Template-Parameter übergebenen Typs T.

Anders Ts kann die beteiligten Semantiken drastisch verändern.

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


Die zwei verschiedenen Szenarien:

  • Wenn wir das Funktions-Template mit Typ instanziieren X, wie in (C), wir werden eine Erklärung von a Zeiger auf int genannt x, aber;

  • wenn wir die Vorlage mit typ instanziieren Y, wie in (D), (EIN) würde stattdessen aus einem Ausdruck bestehen, der das Produkt von berechnet 123 multipliziert mit einigen bereits deklarierten Variablen x.



DAS GRUNDPRINZIP

Der C ++ Standard kümmert sich zumindest in diesem Fall um unsere Sicherheit und unser Wohlbefinden.

Um zu verhindern, dass eine Implementierung möglicherweise unangenehme Überraschungen erleidet, fordert der Standard, dass wir die Mehrdeutigkeit von a abhängiger Name durch ausdrücklich Wir geben die Absicht an, wo immer wir den Namen als a behandeln möchten Modellname, oder ein Vorlagen-ID.

Wenn nichts angegeben ist, die abhängiger Name wird entweder als eine Variable oder als eine Funktion betrachtet.



WIE VERWENDET MAN Abhängige Namen?

Wenn das ein Hollywoodfilm wäre, abhängige Namen wäre die Krankheit, die sich durch Körperkontakt ausbreitet, sofort ihren Wirt beeinflusst, um ihn zu verwirren. Verwirrung, die möglicherweise zu einem schlecht geformten perso-, erhm .. Programm führen könnte.

EIN abhängiger Name ist irgendein Name, der direkt oder indirekt von a abhängt Vorlagenparameter.

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    
}

Wir haben vier abhängig Namen im obigen Snippet:

  • E)
    • "Art" hängt von der Instantiierung ab SomeTrait<T>, die einschließen T, und;
  • F)
    • "NestedTrait", die ein Vorlagen-ID, kommt drauf an SomeTrait<T>, und;
    • "Art" am Ende von (F) kommt drauf an NestedTrait, die davon abhängen SomeTrait<T>, und;
  • G)
    • "Daten", die wie ein aussieht Element-Funktionsvorlage, ist indirekt ein abhängiger Name seit der Art von foo hängt von der Instantiierung ab SomeTrait<T>.

Weder der Aussage (E), (F) oder (G) ist gültig, wenn der Compiler den abhängige Namen als Variablen / Funktionen (was, wie gesagt, passiert, wenn wir nicht explizit etwas anderes sagen).

DIE LÖSUNG

Zu machen g_tmpl eine gültige Definition haben, müssen wir dem Compiler explizit mitteilen, dass wir einen Typ erwarten (E), ein Vorlagen-ID und ein Art im (F), und ein Vorlagen-ID im (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
}

Jedes Mal, wenn Name bezeichnet einen Typ, alle  Namen beteiligt sein muss entweder Typnamen oder NamespacesIn diesem Sinne ist es ziemlich leicht zu sehen, dass wir uns bewerben typename zu Beginn unserer voll qualifizierter Name.

template ist jedoch in dieser Hinsicht anders, da es keine Möglichkeit gibt, zu einer Schlussfolgerung zu kommen, wie z. "Oh, das ist eine Vorlage, als diese andere Sache muss auch eine Vorlage sein". Dies bedeutet, dass wir uns bewerben template direkt vor jedem Name das würden wir gerne so behandeln.



KANN ICH GERADE STICKEN SCHLÜSSELWÖRTER VOR NAME?

"Kann ich einfach bleiben? typename und template vor irgendeinem Namen? Ich möchte mich nicht um den Kontext kümmern, in dem sie erscheinen ..."- Some C++ Developer

Die Regeln im Standard geben an, dass Sie die Schlüsselwörter anwenden können, solange Sie mit einem handeln qualifizierter Name (K), aber wenn der Name nicht ist qualifiziert die Bewerbung ist schlecht formuliert (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

Hinweis: Bewirbt sich typename oder template in einem Kontext, in dem es nicht erforderlich ist, gilt nicht als gute Praxis; nur weil du etwas tun kannst, bedeutet das nicht, dass du es tun solltest.


Zusätzlich gibt es Kontexte wo typename und template sind ausdrücklich nicht erlaubt:

  • Bei Angabe der Basen, von denen eine Klasse erbt

    Jeder Name in einer abgeleiteten Klasse geschrieben Basis-Spezifizierer-Liste wird bereits als a behandelt Modellname, explizit angeben typename ist sowohl schlecht als auch überflüssig.

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


  • Wenn das Vorlagen-ID ist diejenige, auf die in einer abgeleiteten Klasse Bezug genommen wird Benutzungs-Direktive

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

73
2018-06-07 20:28



typedef typename Tail::inUnion<U> dummy;

Ich bin mir jedoch nicht sicher, ob die Implementierung von inUnion korrekt ist. Wenn ich richtig verstehe, sollte diese Klasse nicht instanziiert werden, daher wird der Tab "fail" niemals fehlschlagen. Vielleicht wäre es besser zu zeigen, ob der Typ in der Vereinigung ist oder nicht mit einem einfachen booleschen Wert.

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: Schau dir das an Boost :: Variante

PS2: Schau dir das an Typlisteninsbesondere in Andrei Alexandrescus Buch: Modern C ++ Design


17
2018-03-04 13:37



Diese Antwort soll eher kurz und knapp sein, um die betitelte Frage zu beantworten. Wenn Sie eine Antwort mit mehr Details wünschen, die erklärt, warum Sie sie dort hinstellen müssen, gehen Sie bitte Hier.


Die allgemeine Regel für das Setzen der typename Das Schlüsselwort wird meistens verwendet, wenn Sie einen Vorlagenparameter verwenden und auf ein verschachteltes Objekt zugreifen möchten typedef oder using-alias, zum Beispiel:

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

Beachten Sie, dass dies auch für Metafunktionen oder Dinge gilt, die auch generische Vorlagenparameter verwenden. Wenn der bereitgestellte Vorlagenparameter jedoch ein expliziter Typ ist, müssen Sie ihn nicht angeben typename, beispielsweise:

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

Die allgemeinen Regeln zum Hinzufügen der template Qualifikationsmerkmal sind meistens ähnlich, außer dass sie typischerweise Template-Funktionen (statisch oder anders) einer Struktur / Klasse beinhalten, die selbst templated ist, zum Beispiel:

Angesichts dieser Struktur und Funktion:

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
}

Zugriff versuchen t.get<int>() von innen die Funktion wird zu einem Fehler führen:

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

In diesem Zusammenhang würden Sie also die template Stichwort vorher und nenne es so:

t.template get<int>()

Auf diese Weise wird der Compiler das richtig analysieren und nicht t.get < int.


14
2018-06-06 22:23