1388 Stimmen

Wo und warum muss ich die Schlüsselwörter "template" und "typename" einfügen?

Wo und warum muss ich in Vorlagen typename y template auf abhängige Namen?
Was genau sind eigentlich abhängige Namen?

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, liegt in der typedef Tail::inUnion<U> dummy Linie. Ich bin mir ziemlich sicher, dass inUnion ist ein abhängiger Name, und VC++ hat völlig Recht, wenn es sich daran stört.
Ich weiß auch, dass ich in der Lage sein sollte, Folgendes hinzuzufügen template irgendwo, um dem Compiler mitzuteilen, dass inUnion eine Template-ID ist. Aber wo genau? Und sollte er dann annehmen, dass inUnion eine Klassenvorlage ist, d. h. inUnion<U> einen Typ und nicht eine Funktion nennt?

1434voto

Johannes Schaub - litb Punkte 479831

(Siehe hier auch meine C++11-Antwort )

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

t * f;

Wie sollte dies analysiert werden? In vielen Sprachen braucht ein Compiler die Bedeutung eines Namens nicht zu kennen, um ihn zu analysieren und im Grunde zu wissen, welche Aktion eine Codezeile ausführt. In C++ kann das obige jedoch zu sehr unterschiedlichen Interpretationen führen, je nachdem, was t bedeutet. Wenn es ein Typ ist, dann ist es eine Deklaration eines Zeigers f . Wenn es sich jedoch nicht um einen Typ handelt, wird es eine Multiplikation sein. So sagt der C++ Standard in Absatz (3/7):

Einige Namen bezeichnen Typen oder Vorlagen. Im Allgemeinen muss bei jedem Namen festgestellt werden, ob er eine dieser Entitäten bezeichnet, bevor das Programm, in dem er vorkommt, weiter geparst werden kann. Der Prozess, der dies feststellt, wird als Name Lookup bezeichnet.

Wie wird der Compiler herausfinden, was ein Name t::x bezieht sich auf, wenn t auf einen Parameter des Vorlagentyps verweist? x könnte ein statisches int-Datenelement sein, das multipliziert werden könnte, oder es könnte ebenso gut eine verschachtelte Klasse oder ein typedef sein, das zu einer Deklaration führen könnte. Wenn ein Name diese Eigenschaft hat - dass er nicht nachgeschlagen werden kann, bevor die tatsächlichen Vorlagenargumente bekannt sind - dann wird er als abhängiger Name (es "hängt" von den Parametern der Vorlage ab).

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

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

Dies wird funktionieren und ist sogar von der Norm als möglicher Implementierungsansatz zugelassen. Diese Compiler kopieren im Grunde den Text der Vorlage in einen internen Puffer, und nur wenn eine Instanziierung erforderlich ist, analysieren sie die Vorlage und erkennen möglicherweise Fehler in der Definition. Anstatt jedoch die Benutzer der Vorlage (die armen Kollegen!) mit Fehlern zu belästigen, die vom Autor der Vorlage gemacht wurden, entscheiden sich andere Implementierungen dafür, Vorlagen frühzeitig zu prüfen und Fehler in der Definition so früh wie möglich zu melden, 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 "typename

Die Antwort lautet: Wir entscheiden, wie der Compiler dies parsen soll. Wenn t::x ein abhängiger Name ist, dann müssen wir ihm das Präfix typename um dem Compiler mitzuteilen, dass er den Text auf eine bestimmte Weise analysieren soll. Der Standard sagt in (14.6/2):

Bei einem Namen, der in einer Schablonendeklaration oder -definition verwendet wird und von einem Schablonenparameter abhängig ist, wird nicht als Name eines Typs, es sei denn, die entsprechende Namenssuche findet einen Typnamen oder der Name ist durch das Schlüsselwort durch das Schlüsselwort typename.

Es gibt viele Namen, für die typename ist nicht notwendig, da der Compiler mit der entsprechenden Namenssuche in der Schablonendefinition selbst herausfinden kann, wie ein Konstrukt zu parsen ist - zum Beispiel mit T *f; wenn T ist ein Parameter der Typvorlage. Aber für t::x * f; eine Erklärung zu sein, muss sie wie folgt geschrieben werden typename t::x *f; . Wenn Sie das Schlüsselwort weglassen und der Name als Nicht-Typ angesehen wird, aber bei der Instanziierung festgestellt wird, dass er einen Typ bezeichnet, werden die üblichen Fehlermeldungen vom Compiler ausgegeben. Manchmal wird der Fehler folglich zur Definitionszeit angegeben:

// 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 wird daher davon ausgegangen, dass sich unqualifizierte Namen immer auf Typen beziehen, wenn sie dies tun.

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

Das Schlüsselwort "Vorlage"

Erinnern Sie sich noch an das obige Zitat und daran, dass der Standard auch für Vorlagen eine besondere Handhabung verlangt? Nehmen wir das folgende unschuldig aussehende Beispiel:

boost::function< int() > f;

Für einen menschlichen Leser mag das offensichtlich erscheinen. Nicht so für den Compiler. Stellen Sie sich die folgende willkürliche Definition von boost::function y f :

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

Das ist eigentlich eine gültige Ausdruck ! Er verwendet den less-than-Operator zum Vergleich boost::function gegen Null ( int() ), und vergleicht dann mit dem Größer-als-Operator das Ergebnis bool gegen f . Aber wie Sie vielleicht wissen, boost::function im wirklichen Leben ist eine Vorlage, so dass der Compiler weiß (14.2/3):

Nachdem name lookup (3.4) festgestellt hat, dass ein Name ein Template-Name ist, wird, wenn dieser Name von einem < gefolgt wird, das < immer als Anfang einer Liste von Schablonenargumenten und niemals als Name, gefolgt von dem less-than Operator.

Jetzt sind wir wieder bei demselben Problem wie bei typename . Was, wenn wir beim Parsen des Codes noch nicht wissen können, ob der Name eine Vorlage ist? Wir müssen Folgendes einfügen template unmittelbar vor dem Namen der Vorlage, wie durch 14.2/4 . Das sieht so aus:

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

Vorlagennamen können nicht nur nach einem :: sondern auch nach einer -> o . in einem Zugriff auf ein Klassenmitglied. Sie müssen das Schlüsselwort auch dort einfügen:

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

Abhängigkeiten

Für die Leute, die dicke Standardbücher in ihrem Regal stehen haben und wissen wollen, wovon ich genau spreche, werde ich ein wenig darüber sprechen, wie dies in der Norm spezifiziert ist.

In Schablonendeklarationen haben einige Konstrukte unterschiedliche Bedeutungen, je nachdem, welche Schablonenargumente Sie zur Instanziierung der Schablone verwenden: Ausdrücke können unterschiedliche Typen oder Werte haben, Variablen können unterschiedliche Typen haben oder Funktionsaufrufe können unterschiedliche Funktionen aufrufen. Solche Konstrukte werden im Allgemeinen als abhängen zu den Parametern der Vorlage.

Der Standard definiert genau die Regeln, nach denen ein Konstrukt abhängig ist oder nicht. Er trennt sie in logisch unterschiedliche Gruppen: Eine fängt Typen, eine andere fängt Ausdrücke. Ausdrücke können von ihrem Wert und/oder ihrem Typ abhängig sein. So haben wir, mit typischen Beispielen im Anhang:

  • 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.: ein Cast auf einen Typvorlagenparameter (T)0 )

Die meisten Regeln sind intuitiv und werden rekursiv aufgebaut: Zum Beispiel kann ein Typ, der als T[N] ist ein abhängiger Typ, wenn N ein wertabhängiger Ausdruck ist oder T ist ein abhängiger Typ. Die Einzelheiten hierzu sind im Abschnitt zu lesen (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 wenig unklar darüber, was genau ist eine abhängiger Name . Bei einfacher Lektüre (Sie wissen schon, das Prinzip der geringsten Überraschung) ist alles, was er definiert, ein abhängiger Name ist der Sonderfall für die folgenden Funktionsnamen. Da aber eindeutig T::x auch im Instanziierungskontext nachgeschlagen werden muss, muss es sich auch um einen abhängigen Namen handeln (glücklicherweise hat das Komitee Mitte C++14 damit 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, stellt eine Teilmenge Namen dar. Diese Namen sind daher "abhängige Namen". Ein Name kann verschiedene Formen annehmen - sagt der Standard:

Ein Name ist die Verwendung eines Bezeichners (2.11), einer operator-function-id (13.5), einer conversion-function-id (12.3.2) oder einer template-id (14.2), die eine Entität oder ein Label bezeichnet (6.6.4, 6.1)

Ein Bezeichner ist nur eine einfache Folge von Zeichen/Ziffern, während die nächsten beiden die operator + y operator type Formular. Die letzte Form ist template-name <argument list> . All dies sind Namen, und wie im Standard üblich, kann ein Name auch Qualifizierer enthalten, die angeben, in welchem Namespace oder welcher Klasse ein Name nachgeschlagen werden soll.

Ein wertabhängiger Ausdruck 1 + N ist kein Name, sondern N ist. Die Teilmenge aller abhängigen Konstrukte, die Namen sind, heißt abhängiger Name . Funktionsnamen können jedoch in verschiedenen Instanziierungen einer Vorlage unterschiedliche Bedeutung haben, fallen aber leider nicht unter diese allgemeine Regel.

Abhängige Funktionsnamen

Dies ist zwar nicht das Hauptanliegen dieses Artikels, aber dennoch erwähnenswert: Funktionsnamen sind eine Ausnahme, die gesondert behandelt werden. Ein Bezeichner-Funktionsname ist nicht von sich selbst abhängig, sondern von den typabhängigen Argumentausdrücken, die in einem Aufruf verwendet werden. In dem Beispiel f((T)0) , f ist ein abhängiger Name. Im Standard wird dies angegeben unter (14.6.2/1) .

Zusätzliche Hinweise und Beispiele

In genügend Fällen brauchen wir beide typename y 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 stehen. Er kann auch in der Mitte vor einem Klassennamen stehen, 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 verboten, wie im Folgenden beschrieben

  • Im Namen einer abhängigen Basisklasse dürfen Sie nicht schreiben typename . Es wird davon ausgegangen, dass der angegebene Name ein Klassentypname ist. Dies gilt sowohl für die Namen in der Basisklassenliste als auch in der Liste der Konstruktorinitialisierer:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
  • In using-Deklarationen ist es nicht möglich, die template nach der letzten :: und der C++-Ausschuss sagte nicht an einer Lösung zu 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
     };

171voto

C++11

Problem

Während die Regeln in C++03 darüber, wann man eine typename y template weitgehend vernünftig sind, gibt es einen ärgerlichen Nachteil ihrer 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 sieht, brauchen wir das Disambiguierungs-Schlüsselwort auch dann, wenn der Compiler selbst perfekt herausfinden könnte, dass A::result_type kann nur sein int (und ist damit ein Typ), und this->g kann nur die Mitgliedsvorlage g später erklärt (auch wenn A explizit irgendwo spezialisiert ist, würde sich das nicht auf den Code innerhalb dieser Vorlage auswirken, so dass seine Bedeutung nicht durch eine spätere Spezialisierung von A !).

Aktuelle Instanziierung

Um die Situation zu verbessern, verfolgt die Sprache in C++11, wann ein Typ auf die einschließende Vorlage verweist. Um das zu wissen, muss der Typ unter Verwendung einer bestimmten Form von Name gebildet worden sein, die sein eigener Name ist (im obigen Beispiel, A , A<T> , ::A<T> ). Ein Typ, der durch einen solchen Namen referenziert wird, ist bekanntlich der aktuelle Instanziierung . Es kann mehrere Typen geben, die alle die aktuelle Instanziierung sind, wenn der Typ, aus dem der Name gebildet wird, eine Mitglieds-/Schachtelklasse ist (dann, A::NestedClass y A sind beide aktuelle Instanzen).

Auf der Grundlage dieses Begriffs heißt es in der Sprache CurrentInstantiation::Foo , Foo y CurrentInstantiationTyped->Foo (wie zum Beispiel A *a = this; a->Foo ) sind alle Mitglied der aktuellen Instanziierung wenn sie als Mitglieder einer Klasse gefunden werden, die die aktuelle Instanziierung oder eine ihrer nicht abhängigen Basisklassen ist (indem man einfach den Namen sofort nachschlägt).

Die Schlüsselwörter typename y template sind nun nicht mehr erforderlich, wenn der Qualifier ein Mitglied der aktuellen Instanzierung ist. Ein wichtiger Punkt, den man sich hier merken sollte, ist, dass A<T> es todavía einen typabhängigen Namen (immerhin T ist ebenfalls typabhängig). Aber A<T>::result_type bekannt ist, ein Typ zu sein - der Compiler wird diese Art von abhängigen Typen "magisch" untersuchen, um dies 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 noch besser werden? Die Sprache geht sogar noch weiter und erfordert dass eine Implementierung wiederum nachschlägt D::result_type bei der Instanziierung D::f (auch wenn es seine Bedeutung bereits zur Definitionszeit gefunden hat). Wenn nun das Ergebnis der Suche abweicht oder mehrdeutig ist, ist das Programm fehlerhaft und es muss eine Diagnose gestellt werden. Stellen Sie sich vor, was passiert, wenn wir definieren C wie diese

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

Ein Compiler ist erforderlich, um den Fehler bei der Instanziierung zu erkennen D<int>::f . So erhalten Sie das Beste aus zwei Welten: Die "verzögerte" Suche schützt Sie, wenn Sie mit abhängigen Basisklassen in Schwierigkeiten geraten könnten, und die "sofortige" Suche befreit Sie von typename y template .

Unbekannte Spezialisierungen

Im Kodex der D der Name typename D::questionable_type kein Mitglied der aktuellen Instanziierung ist. Stattdessen markiert die Sprache es als Mitglied einer unbekannten Spezialisierung . Dies ist insbesondere immer dann der Fall, wenn Sie Folgendes tun DependentTypeName::Foo o DependentTypedName->Foo und entweder ist der abhängige Typ no die aktuelle Instanziierung (in diesem Fall kann der Compiler aufgeben und sagen "wir werden später schauen, was Foo ist) oder es ist die aktuelle Instanziierung und der Name wurde 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 haben h innerhalb der oben definierten A Klassenvorlage

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

In C++03 erlaubte die Sprache, diesen Fehler abzufangen, weil es niemals eine gültige Möglichkeit geben konnte, die A<T>::h (welches Argument Sie auch immer für T ). In C++11 verfügt die Sprache nun über eine weitere Prüfung, um den Compilern mehr Gründe für die Implementierung dieser Regel zu geben. Seit A hat keine abhängigen Basisklassen, und A erklärt, dass kein Mitglied questionable_type der Name A<T>::questionable_type es weder ein Mitglied der aktuellen Instanziierung noch ein Mitglied einer unbekannten Spezialisierung. In diesem Fall sollte es keine Möglichkeit geben, dass dieser Code zum Zeitpunkt der Instanziierung gültig kompiliert werden kann. Daher verbietet die Sprache, dass ein Name, dessen Qualifizierer die aktuelle Instanziierung ist, weder Mitglied einer unbekannten Spezialisierung noch Mitglied der aktuellen Instanziierung ist (allerdings muss dieser Verstoß noch nicht diagnostiziert werden).

Beispiele und Trivialitäten

Sie können dieses Wissen testen an 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 schlecht formuliert (was vom C++-Ausschuss 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 this->f a A::f zum Zeitpunkt der Instanziierung und alles ist in Ordnung. C++11 jedoch bindet es sofort an B::f und erfordert eine doppelte Überprüfung bei der Instanziierung, um zu prüfen, ob der Lookup noch passt. Bei der Instanziierung jedoch C<A>::g die Dominanz-Regel gilt und die Suche findet A::f stattdessen.

147voto

Filip Roséen - refp Punkte 60138

Vorwort

Dieser Beitrag ist als Anregung gedacht leicht zu lesen alternativ zu litb's post .

Der eigentliche Zweck ist derselbe: eine Erklärung für das "Wann?" und "Warum?". typename y template angewendet werden müssen.


Was ist der Zweck von typename y template ?

typename y template sind auch unter anderen Umständen als bei der Deklaration einer Vorlage verwendbar.

Es gibt bestimmte Kontexte, in denen C++ in denen dem Compiler explizit mitgeteilt werden muss, wie ein Name zu behandeln ist, und alle diese Kontexte haben eines gemeinsam: Sie hängen von mindestens einem Vorlage-Parameter .

Wir bezeichnen solche Bezeichnungen, bei denen eine Mehrdeutigkeit in der Auslegung möglich ist, als: " abhängige Namen ".

In diesem Beitrag wird die Beziehung zwischen abhängige-namen und die beiden Schlüsselwörter.


Ein Ausschnitt sagt mehr als 1000 Worte

Versuchen Sie zu erklären, was im Folgenden vor sich geht Funktionsschablone entweder zu sich selbst, zu einem Freund oder vielleicht zu Ihrer Katze; was geschieht in der mit ( A )?

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

Es ist vielleicht nicht so einfach, wie man denkt, genauer gesagt, das Ergebnis der Auswertung von ( A ) stark hängt bei der Definition des als Template-Parameter übergebenen Typs T .

Anders T s können die Semantik drastisch verändern.

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

Die beiden unterschiedlichen Szenarien :

  • Wenn wir das Funktions-Template mit dem Typ X , wie in ( C ), haben wir eine Erklärung eines Zeiger-auf int namens x , sondern;

  • wenn wir die Vorlage mit dem Typ Y , wie in ( D ), ( A ) würde stattdessen aus einem Ausdruck bestehen, der das Produkt aus 123 multipliziert mit einer bereits deklarierten Variablen x .


Der Grundgedanke

Der C++ Standard sorgt sich um unsere Sicherheit und unser Wohlergehen, zumindest in diesem Fall.

Um zu verhindern, dass eine Implementierung möglicherweise böse Überraschungen erlebt, schreibt der Standard vor, dass wir die Mehrdeutigkeit einer abhängiger-name von ausdrücklich überall dort, wo wir den Namen als eine der folgenden Bezeichnungen behandeln möchten Typ-Name ou un vorlage-id .

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


Wie sind abhängige Namen zu behandeln?

Wenn dies ein Hollywood-Film wäre, abhängige-namen wäre die Krankheit, die sich durch Körperkontakt ausbreitet, sofort auf ihren Wirt einwirkt und ihn verwirrt. Eine Verwirrung, die möglicherweise zu einem missgebildeten Perso-, ähm Programm führen könnte.

A abhängiger-name es cualquier Name, der direkt oder indirekt von einem Vorlage-Parameter .

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 in dem obigen Ausschnitt:

  • E )
    • "Typ" hängt von der Instanziierung von SomeTrait<T> die Folgendes umfassen T , und;
  • F )
    • "NestedTrait" die ein vorlage-id hängt ab von SomeTrait<T> , und;
    • "Typ" am Ende von ( F ) hängt ab von NestedTrait die abhängt von SomeTrait<T> , und;
  • G )
    • "Daten" die aussieht wie ein Member-Funktionsvorlage ist indirekt ein abhängiger-name da die Art der foo hängt von der Instanziierung von SomeTrait<T> .

Keine der beiden Aussagen ( E ), ( F ) oder ( G ) ist gültig, wenn der Compiler die abhängige-namen als Variablen/Funktionen (was, wie bereits erwähnt, geschieht, wenn wir nicht ausdrücklich etwas anderes sagen).

Die Lösung

Anfertigen g_tmpl eine gültige Definition haben, müssen wir dem Compiler ausdrücklich mitteilen, dass wir einen Typ in ( E ), a vorlage-id und eine Typ in ( F ), und eine vorlage-id in ( 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 ein Name bezeichnet einen Typ, todo Namen beteiligt sind, müssen entweder Typennamen o Namensräume In diesem Sinne ist es ganz einfach zu erkennen, dass wir die typename zu Beginn unserer vollständigen qualifizierter Name .

template ist in dieser Hinsicht jedoch anders, da es keine Möglichkeit gibt, zu einer Schlussfolgerung wie der folgenden zu gelangen; "Oh, das ist eine Vorlage, dann muss dieses andere Ding auch eine Vorlage sein". . Dies bedeutet, dass wir Folgendes anwenden template direkt vor einem beliebigen Name die wir gerne als solche behandeln würden.


Kann ich einfach die Schlüsselwörter vor einem beliebigen Namen?

" Kann ich einfach die typename y template vor einem beliebigen Namen? Ich möchte mir keine Gedanken über den Kontext machen, in dem sie erscheinen... " - Some C++ Developer

Die Regeln im Standard besagen, dass Sie die Schlüsselwörter anwenden können, solange es sich um eine qualifizierter-name ( K ), aber wenn der Name nicht qualifiziert der Antrag ist nicht formgerecht ( 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

Note : Anwendung typename o template in einem Kontext, in dem es nicht erforderlich ist, gilt nicht als gute Praxis; nur weil man etwas tun kann, heißt das nicht, dass man es tun sollte.

Außerdem gibt es Kontexte, in denen typename y template son ausdrücklich verweigert:

  • Bei der Angabe der Basen, von denen eine Klasse erbt

    Jeder Name, der in einer abgeleiteten Klasse in der base-specifier-list wird bereits als Typ-Name , wobei ausdrücklich angegeben wird typename ist sowohl schlecht geformt als auch redundant.

                        // .------- the base-specifier-list
      template<class T> // v
      struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
        ...
      };
  • Wenn die vorlage-id ist diejenige, auf die in der abgeleiteten Klasse der Gebrauchsanweisung

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

35voto

Rapptz Punkte 20107

<em>Diese Antwort ist eher kurz und knapp gehalten, um (einen Teil) der titelgebenden Frage zu beantworten. Wenn Sie eine ausführlichere Antwort wünschen, in der erklärt wird, warum Sie sie dort unterbringen müssen, gehen Sie bitte zu <a href="https://stackoverflow.com/a/613132/1381108">aquí </a>.</em>


Die allgemeine Regel für das Setzen der typename Schlüsselwort wird meist verwendet, wenn Sie einen Vorlagenparameter verwenden und auf eine verschachtelte typedef oder die Verwendung von Aliasen, 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 Meta-Funktionen oder Dinge gilt, die ebenfalls generische Template-Parameter annehmen. Wenn der übergebene Template-Parameter jedoch ein expliziter Typ ist, müssen Sie nicht angeben typename zum Beispiel:

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 für das Hinzufügen der template Qualifier sind größtenteils ähnlich, außer dass sie typischerweise schablonenhafte Mitgliedsfunktionen (statisch oder nicht) einer Struktur/Klasse betreffen, die selbst schablonenhaft 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
}

Zugriffsversuch t.get<int>() innerhalb der Funktion führt zu einem Fehler:

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

In diesem Zusammenhang bräuchten Sie also die template Schlüsselwort vor und rufen es so auf:

t.template get<int>()

Auf diese Weise wird der Compiler dies richtig auswerten und nicht t.get < int .

20voto

Luc Touraille Punkte 76149
typedef typename Tail::inUnion<U> dummy;

Ich bin mir jedoch nicht sicher, ob Ihre Implementierung von inUnion korrekt ist. Wenn ich es richtig verstanden habe, soll diese Klasse nicht instanziiert werden, daher wird die Registerkarte "fail" nie wirklich fehlschlagen. Vielleicht wäre es besser, mit einem einfachen booleschen Wert anzugeben, ob der Typ in der Union ist oder nicht.

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: Werfen Sie einen Blick auf Boost::Variante

PS2: Werfen Sie einen Blick auf Typelisten vor allem in dem Buch von Andrei Alexandrescu: Modernes C++-Design

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X