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




3 Answers

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 obige jedoch sehr unterschiedliche Interpretationen ergeben, je nachdem, was t bedeutet. Wenn es ein Typ ist, dann wird es eine Deklaration eines Zeigers 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, worauf sich ein Name t::x bezieht, wenn er sich auf einen Template-Typparameter bezieht? 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 gelesen werden kann, bis die tatsächlichen Template-Argumente bekannt sind - dann wird er als abhängiger Name bezeichnet (er "hängt" von den Template-Parametern 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; herausfindet 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, wie der Compiler das analysieren soll. Wenn t::x ein abhängiger Name ist, müssen wir ihn mit typename , um dem Compiler typename , dass er in einer bestimmten Weise analysiert werden soll. Der Standard sagt um (14.6 / 2):

Ein Name, der in einer Schablonendeklaration oder -definition verwendet wird und der von einem Schablonenparameter abhängig ist, wird davon ausgegangen, dass er keinen Typ angibt, es sei denn, die anwendbare Namensüberprüfung findet einen Typnamen oder der Name wird durch das Schlüsselwort typename qualifiziert.

Es gibt viele Namen, für die typename nicht notwendig ist, weil der Compiler mit der typename in der Template-Definition herausfinden kann, wie ein Konstrukt selbst zu analysieren ist - zum Beispiel mit T *f; , wenn T ein Typvorlagenparameter ist. Aber für t::x * f; typename t::x *f; eine Deklaration zu sein, muss sie als 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 wird daher als selbstverständlich typename dass unqualifizierte Namen immer auf Typen verweisen, 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 von boost::function und f :

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

Das ist eigentlich ein gültiger Ausdruck ! Es verwendet den Kleiner-als-Operator, um boost::function gegen null ( int() ) zu vergleichen, und verwendet dann den Größer-als-Operator, um das resultierende bool mit f zu vergleichen. Wie Sie vielleicht wissen, ist boost::function im realen Leben eine Vorlage, so dass der Compiler (14.2 / 3) weiß:

Nach dem Namen-Lookup (3.4) findet man, dass ein Name ein Template-Name ist, wenn auf diesen Namen ein <folgt, das <wird immer als Anfang einer Template-Argument-Liste genommen und nie als ein Name gefolgt von dem als Betreiber.

Jetzt sind wir wieder bei demselben 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 die template unmittelbar vor dem Vorlagennamen einfügen, wie in 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 einem -> oder vorkommen . in einem Klassenmitglied Zugriff. 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. Von solchen Konstrukten wird allgemein gesagt, dass sie von Template-Parametern abhängen .

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 (zB: ein Typvorlagenparameter T )
  • Wertabhängige Ausdrücke (zB: ein Nicht-Typ-Template-Parameter N )
  • Typabhängige Ausdrücke (z. B. eine Umwandlung in einen Typvorlagenparameter (T)0 )

Die meisten Regeln sind intuitiv und rekursiv aufgebaut: Ein Typ, der als T[N] konstruiert wird, ist beispielsweise ein abhängiger Typ, wenn N ein wertabhängiger Ausdruck oder T ein abhängiger Typ ist. Die Details dazu können in Abschnitt (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 (14.6.2.3) Ausdrücke (14.6.2.3) .

Abhängige Namen

Der Standard ist ein wenig unklar darüber, was genau ein abhängiger Name ist . Bei einem einfachen Lesevorgang (du weißt schon, das Prinzip der geringsten Überraschung) ist alles, was es als abhängiger Name definiert, der Spezialfall für die unten aufgeführten Funktionsnamen. Aber da T::x auch im Instanziierungskontext eindeutig nachgeschlagen werden muss, muss es auch ein abhängiger Name sein (glücklicherweise hat das Komitee seit Mitte C ++ 14 damit begonnen, herauszufinden, 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 beiden das operator + und operator type Formular sind. Das letzte Formular 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, sondern N ist. Die Teilmenge aller abhängigen Konstrukte, die Namen sind, wird als abhängiger Name bezeichnet . 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) ist f ein abhängiger Name. Im Standard ist dies bei (14.6.2/1) .

Zusätzliche Hinweise und Beispiele

In typename Fällen benötigen wir sowohl typename als auch 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;
    };
    // ...
};

Die Keyword- 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 keinen typename schreiben. 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 using-declarations ist es nicht möglich, eine template nach dem letzten :: , und das C ++ - Committee said nicht an einer Lösung arbeiten sollte.

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

In welchen Templates wo und warum muss typename und template auf abhängige Namen gesetzt werden? 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 Zeile. Ich bin ziemlich sicher, dass inUnion ein abhängiger Name ist, und VC ++ hat recht, inUnion erstickt. Ich weiß auch, dass ich in der Lage sein sollte, eine template irgendwo hinzuzufügen, um dem Compiler mitzuteilen, dass inUnion eine Template-ID ist. Aber wo genau? Und sollte es dann annehmen, dass inUnion eine Klassenvorlage ist, dh inUnion<U> nennt einen Typ und keine Funktion?




VORWORT

Dieser Beitrag soll eine leicht zu lesende Alternative zu Litbs Beitrag sein .

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

Was ist der Zweck von typename und template ?

typename und template sind unter Umständen anders als bei der Deklaration einer Vorlage verwendbar.

In C ++ gibt es bestimmte Kontexte, in denen dem Compiler explizit gesagt werden muss, wie ein Name zu behandeln ist, und alle diese Kontexte haben eines gemeinsam; Sie hängen von mindestens einem Template-Parameter ab .

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ängigen Namen und den beiden Schlüsselwörtern.

Ein SNIPPET sagt mehr als 1000 Worte

Versuchen Sie zu erklären, was in der folgenden Funktionsvorlage vor sich geht , entweder für sich selbst, einen Freund oder vielleicht für Ihre Katze; Was passiert in der mit ( A ) gekennzeichneten Aussage?

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


Es ist vielleicht nicht so einfach, wie man denkt, genauer hängt das Ergebnis der Bewertung von ( A ) stark von der Definition des Typs ab, der als Vorlagenparameter T .

Verschiedene T s können 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 dem Typ X instanziieren, wie in ( C ), haben wir eine Deklaration eines Zeigers zu int mit dem Namen x , aber;

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


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 böse Überraschungen erleidet, schreibt der Standard vor, dass wir die Mehrdeutigkeit eines abhängigen Namens aussortieren, indem wir die Absicht explizit angeben, so dass wir den Namen entweder als Typname oder als Vorlage behandeln möchten. ID .

Wenn nichts angegeben ist, wird der abhängige Name entweder als Variable oder als Funktion betrachtet.


WIE MAN ABHÄNGIGE NAMEN HANDHABT ?

Wenn dies ein Hollywood-Film wäre, würden abhängige Namen die Krankheit sein, 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 ein beliebiger Name, der direkt oder indirekt von einem Template-Parameter abhängt.

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ängige Namen im obigen Snippet:

  • E )
    • "type" hängt von der Instantiierung von SomeTrait<T> , zu der T und;
  • F )
    • "NestedTrait" , eine Template-ID , hängt von SomeTrait<T> , und;
    • "type" am Ende von ( F ) hängt von NestedTrait ab , das von SomeTrait<T> abhängt, und;
  • G )
    • "data" , das wie eine Member-Function-Vorlage aussieht, ist indirekt ein abhängiger Name, da der Typ von foo von der Instanziierung von SomeTrait<T> abhängt.

Keine der Aussagen ( E ), ( F ) oder ( G ) ist gültig, wenn der Compiler die abhängigen Namen als Variablen / Funktionen interpretieren würde (was, wie bereits erwähnt, passiert, wenn wir nicht ausdrücklich etwas anderes sagen).

DIE LÖSUNG

Um g_tmpl eine gültige Definition zu geben, müssen wir dem Compiler ausdrücklich mitteilen, dass wir einen Typ in ( E ), eine Template-ID und einen Typ in ( F ) und eine Template-ID in ( G ) erwarten.

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 einen Typ angibt, müssen alle beteiligten Namen entweder typename oder Namespaces sein . In diesem Sinne ist es ziemlich einfach zu sehen, dass wir am Anfang unseres vollständig qualifizierten Namens typename anwenden.

template jedoch in dieser Hinsicht anders, da es keine Möglichkeit gibt, zu einer Schlussfolgerung wie z. "Oh, das ist eine Vorlage, als diese andere Sache muss auch eine Vorlage sein" . Das bedeutet, dass wir die template direkt vor jedem Namen anwenden, den wir als solchen behandeln möchten.


KANN ICH DIE KEYWORDS VOR JEDEM NAMEN STICKEN ?

" Kann ich typename und template vor einen beliebigen Namen setzen? 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 qualifizierten Namen ( K ) arbeiten, aber wenn der Name nicht qualifiziert ist, ist die Anwendung schlecht gebildet ( 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 : Das Anwenden von typename oder template in einem Kontext, in dem dies nicht erforderlich ist, wird nicht als bewährte typename betrachtet. nur weil du etwas tun kannst, bedeutet das nicht, dass du es tun solltest.


Zusätzlich gibt es Kontexte, in denen typename und template explizit nicht typename sind:

  • Bei Angabe der Basen, von denen eine Klasse erbt

    Jeder Name, der in der typename einer abgeleiteten Klasse geschrieben ist, wird bereits als typename behandelt. Explizite Angabe von typename ist sowohl schlecht als auch redundant.

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


  • Wenn die Template-ID diejenige ist, auf die in der using-Direktive einer abgeleiteten Klasse verwiesen wird

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



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 here .

Die allgemeine Regel für das Keyword typename lautet meist, wenn Sie einen Vorlagenparameter verwenden und auf einen geschachtelten typedef oder using-alias zugreifen möchten. 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 nicht typename angeben, z.

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 des template sind größtenteils ähnlich, mit Ausnahme der Tatsache, dass sie in der Regel Vorlagenfunktionen (statisch oder nicht) einer Struktur / Klasse enthalten, die selbst als Vorlage dient, z.

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
}

Der Versuch, aus der Funktion auf t.get<int>() zuzugreifen, t.get<int>() 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 benötigen Sie das template Keyword und nennen es so:

t.template get<int>()

Auf diese Weise wird der Compiler dies richtig analysieren, anstatt t.get < int .




Related

c++ templates typename c++-faq dependent-name