In Vorlagen, wo und warum muss ich setzen typename
und template
auf abhängige Namen?
Was genau sind abhängige Namen überhaupt?
Ich habe 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
Leitung. Ich bin mir ziemlich sicher, dass dies inUnion
ein abhängiger Name ist, und VC ++ ist völlig richtig darin, daran zu ersticken.
Ich weiß auch, dass ich template
irgendwo etwas hinzufügen sollte , um dem Compiler mitzuteilen, dass inUnion eine Vorlagen-ID ist. Aber wo genau? Und sollte es dann annehmen, dass inUnion eine Klassenvorlage ist, dh inUnion<U>
einen Typ und keine Funktion benennt?
Antworten:
(Siehe hier auch für meine C ++ 11 Antwort )
Um ein C ++ - Programm zu analysieren, muss der Compiler wissen, ob bestimmte Namen Typen sind oder nicht. Das folgende Beispiel zeigt Folgendes:
Wie soll das analysiert 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 zu sehr unterschiedlichen Interpretationen führen, je nachdem, was
t
bedeutet. Wenn es sich um einen Typ handelt, handelt es sich um eine Deklaration eines Zeigersf
. Wenn es sich jedoch nicht um einen Typ handelt, handelt es sich um eine Multiplikation. Der C ++ Standard sagt also in Absatz (3/7):Wie findet der Compiler heraus, worauf sich ein Name
t::x
bezieht, wenn ert
sich auf einen Vorlagentypparameter bezieht?x
könnte ein statisches int-Datenelement sein, das multipliziert werden könnte, oder könnte ebenso gut eine verschachtelte Klasse oder ein typedef sein, das einer Deklaration nachgeben könnte. Wenn ein Name diese Eigenschaft hat - dass er erst nachgeschlagen werden kann, wenn die tatsächlichen Vorlagenargumente bekannt sind -, wird er als abhängiger Name bezeichnet (dies hängt von den Vorlagenparametern ab).Sie können empfehlen, nur zu warten, bis der Benutzer die Vorlage instanziiert:
Dies wird funktionieren und wird vom Standard als möglicher Implementierungsansatz tatsächlich zugelassen. Diese Compiler kopieren den Text der Vorlage grundsätzlich in einen internen Puffer. Nur wenn eine Instanziierung erforderlich ist, analysieren sie die Vorlage und erkennen möglicherweise Fehler in der Definition. Anstatt die Benutzer der Vorlage (arme Kollegen!) Mit Fehlern des Autors einer Vorlage zu belästigen, prüfen andere Implementierungen die Vorlagen frühzeitig und geben so schnell wie möglich Fehler in der Definition an, bevor überhaupt eine Instanziierung stattfindet.
Es muss also eine Möglichkeit geben, dem Compiler mitzuteilen, dass bestimmte Namen Typen sind und bestimmte Namen nicht.
Das Schlüsselwort "Typname"
Die Antwort lautet: Wir entscheiden, wie der Compiler dies analysieren soll. Wenn
t::x
es sich um einen abhängigen Namen handelt, müssen wir ihm ein Präfix voranstellen, damittypename
der Compiler ihn auf eine bestimmte Weise analysieren kann. Der Standard sagt bei (14.6 / 2):Es gibt viele Namen, für die dies
typename
nicht erforderlich ist, da der Compiler mit der entsprechenden Namenssuche in der Vorlagendefinition herausfinden kann, wie ein Konstrukt selbst analysiert wird - beispielsweise mitT *f;
, wennT
es sich um einen Typvorlagenparameter handelt. Abert::x * f;
um eine Erklärung zu sein, muss sie wie folgt geschrieben seintypename t::x *f;
. Wenn Sie das Schlüsselwort weglassen und der Name als Nicht-Typ angesehen wird, die Instanziierung jedoch feststellt, dass es sich um einen Typ handelt, werden die üblichen Fehlermeldungen vom Compiler ausgegeben. Manchmal wird der Fehler folglich zur Definitionszeit angegeben:Die Syntax erlaubt
typename
nur vor qualifizierten Namen - es wird daher als selbstverständlich angesehen, dass nicht qualifizierte Namen immer bekanntermaßen auf Typen verweisen, wenn sie dies tun.Ein ähnliches Gotcha gibt es für Namen, die Vorlagen bezeichnen, wie im Einführungstext angedeutet.
Das Schlüsselwort "Vorlage"
Erinnern Sie sich an das erste Zitat oben und wie der Standard auch für Vorlagen eine spezielle Behandlung erfordert? Nehmen wir das folgende unschuldig aussehende Beispiel:
Für einen menschlichen Leser mag es offensichtlich erscheinen. Nicht so beim Compiler. Stellen Sie sich die folgende willkürliche Definition von
boost::function
und vorf
:Das ist eigentlich ein gültiger Ausdruck ! Es verwendet den Operator kleiner als zum Vergleichen
boost::function
mit Null (int()
) und verwendet dann den Operator größer als zum Vergleichen des Ergebnissesbool
mitf
. Wie Sie vielleicht wissen, handelt es sichboost::function
im wirklichen Leben um eine Vorlage, sodass der Compiler weiß (14.2 / 3):Jetzt sind wir wieder bei dem gleichen Problem wie bei
typename
. Was ist, wenn wir beim Parsen des Codes noch nicht wissen können, ob der Name eine Vorlage ist? Wir müssentemplate
unmittelbar vor dem Vorlagennamen einfügen , wie von angegeben14.2/4
. Das sieht so aus:Vorlagennamen können nicht nur nach einem,
::
sondern auch nach einem->
oder.
in einem Klassenmitgliedszugriff auftreten. Dort müssen Sie auch das Schlüsselwort einfügen:Abhängigkeiten
Für die Leute, die dicke Standardese-Bücher in ihrem Regal haben und wissen wollen, worüber ich genau gesprochen habe, werde ich ein wenig darüber sprechen, wie dies im Standard festgelegt ist.
In Vorlagendeklarationen haben einige Konstrukte unterschiedliche Bedeutungen, je nachdem, welche Vorlagenargumente Sie zum Instanziieren der Vorlage 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 sollen im Allgemeinen von Schablonenparametern abhängen .
Der Standard definiert genau die Regeln, ob ein Konstrukt abhängig ist oder nicht. Es unterteilt sie in logisch unterschiedliche Gruppen: Einer fängt Typen ab, ein anderer fängt Ausdrücke ab. Ausdrücke können von ihrem Wert und / oder ihrem Typ abhängen. Wir haben also mit typischen Beispielen Folgendes angehängt:
T
)N
)(T)0
)Die meisten Regeln sind intuitiv und rekursiv aufgebaut: Beispielsweise ist ein Typ, der als
T[N]
abhängiger Typ konstruiert ist, einN
wertabhängiger Ausdruck oderT
ein abhängiger Typ. Die Details hierzu finden Sie im 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 wertabhängige Ausdrücke.Abhängige Namen
Der Standard ist etwas unklar, was genau ein abhängiger Name ist . Bei einem einfachen Lesevorgang (Sie wissen, das Prinzip der geringsten Überraschung) ist alles, was als abhängiger Name definiert wird, der Sonderfall für Funktionsnamen unten. Da
T::x
dies natürlich auch im Instanziierungskontext nachgeschlagen werden muss, muss es sich auch um einen abhängigen Namen handeln (glücklicherweise hat das Komitee ab Mitte C ++ 14 begonnen, zu prüfen, 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 Namen. Diese Namen sind daher "abhängige Namen". Ein Name kann verschiedene Formen annehmen - der Standard sagt:
Ein Bezeichner ist nur eine einfache Folge von Zeichen / Ziffern, während die nächsten beiden die
operator +
undoperator type
-Form sind. Die letzte Form isttemplate-name <argument list>
. All dies sind Namen, und bei herkömmlicher Verwendung im Standard kann ein Name auch Qualifizierer enthalten, die angeben, in welchem Namespace oder in welcher Klasse ein Name gesucht werden soll.Ein wertabhängiger Ausdruck
1 + N
ist kein Name, sondern ein NameN
. Die Teilmenge aller abhängigen Konstrukte, bei denen es sich um Namen handelt, wird als abhängiger Name bezeichnet . Funktionsnamen können jedoch in verschiedenen Instanziierungen einer Vorlage unterschiedliche Bedeutungen haben, werden jedoch von dieser allgemeinen Regel leider nicht erfasst.Abhängige Funktionsnamen
Nicht in erster Linie ein Anliegen dieses Artikels, aber dennoch erwähnenswert: Funktionsnamen sind eine Ausnahme, die separat behandelt werden. Der Name einer Bezeichnerfunktion hängt nicht von sich selbst ab, 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 ist dies unter angegeben(14.6.2/1)
.Zusätzliche Hinweise und Beispiele
In genügend Fällen brauchen wir sowohl von
typename
als auchtemplate
. Ihr Code sollte wie folgt aussehenDas Schlüsselwort
template
muss nicht immer im letzten Teil eines Namens stehen. Es kann in der Mitte vor einem Klassennamen erscheinen, der wie im folgenden Beispiel als Bereich verwendet wirdIn einigen Fällen sind die Schlüsselwörter verboten, wie unten beschrieben
Auf den Namen einer abhängigen Basisklasse dürfen Sie nicht schreiben
typename
. Es wird angenommen, dass der angegebene Name ein Klassentypname ist. Dies gilt sowohl für Namen in der Basisklassenliste als auch in der Konstruktorinitialisiererliste:In using-Deklarationen ist es nicht möglich,
template
nach dem letzten zu verwenden::
, und das C ++ - Komitee sagte, dass es nicht an einer Lösung arbeiten soll.quelle
typename
erzwungen wird, selbst wenn die Syntax zu diesem Zeitpunkt keine anderen alternativen Interpretationen als Typnamen zulässt?C ++ 11
Problem
Während die Regeln in C ++ 03 festlegen, wann Sie sie benötigen
typename
undtemplate
weitgehend vernünftig sind, gibt es einen ärgerlichen Nachteil bei der FormulierungWie zu sehen ist, benötigen wir das Schlüsselwort "Disambiguierung", selbst wenn der Compiler sich selbst perfekt herausfinden könnte, dass
A::result_type
dies nur sein kannint
(und daher ein Typ ist) undthis->g
nur dieg
später deklarierte Mitgliedsvorlage sein kann (selbst wenn diesA
irgendwo explizit spezialisiert ist) den Code in dieser Vorlage nicht beeinflussen, daher kann seine Bedeutung durch eine spätere Spezialisierung vonA
!) nicht beeinflusst werden.Aktuelle Instanziierung
Um die Situation zu verbessern, verfolgt die Sprache in C ++ 11, wenn sich ein Typ auf die einschließende Vorlage bezieht. Um zu wissen , dass, muss der Typ unter Verwendung eine bestimmte Form des Namen gebildet worden war, die ihr eigener Name ist (in den oben genannten,
A
,A<T>
,::A<T>
). Ein Typ, auf den ein solcher Name verweist, ist als aktuelle Instanziierung bekannt . Es kann mehrere Typen geben, die die gesamte aktuelle Instanziierung darstellen, wenn der Typ, aus dem der Name gebildet wird, ein Mitglied / eine verschachtelte Klasse ist (dannA::NestedClass
undA
beide aktuelle Instanziierungen).Basierend auf dieser Idee, sagt die Sprache , das
CurrentInstantiation::Foo
,Foo
undCurrentInstantiationTyped->Foo
(wieA *a = this; a->Foo
) sind alle Mitglieder der aktuellen Instanziierung , wenn sie gefunden werden , Mitglieder einer Klasse zu sein , dass die aktuelle Instantiierung ist oder eines seiner Nicht-abhängigen Basisklassen (von nur tun , die Namenssuche sofort).Die Schlüsselwörter
typename
undtemplate
werden jetzt nicht mehr benötigt, wenn das Qualifikationsmerkmal Mitglied der aktuellen Instanziierung ist. Ein wichtiger Punkt hierbei ist, dassA<T>
es sich immer noch um einen typabhängigen Namen handelt (schließlichT
ist er auch typabhängig). EsA<T>::result_type
ist jedoch bekannt, dass es sich um einen Typ handelt - der Compiler wird diese Art von abhängigen Typen "magisch" untersuchen, um dies herauszufinden.Das ist beeindruckend, aber können wir es besser machen? Die Sprache geht sogar noch weiter und erfordert, dass eine Implementierung
D::result_type
beim Instanziieren erneut nachgeschlagen wirdD::f
(auch wenn sie ihre Bedeutung bereits zum Zeitpunkt der Definition gefunden hat). Wenn sich das Suchergebnis jetzt unterscheidet oder zu Mehrdeutigkeiten führt, ist das Programm fehlerhaft und es muss eine Diagnose gestellt werden. Stellen Sie sich vor was passiert , wenn wir definiert geschiehtC
wie folgt ausEin Compiler ist erforderlich, um den Fehler beim Instanziieren abzufangen
D<int>::f
. So erhalten Sie das Beste aus zwei Welten: "Verzögerte" Suche, die Sie schützt, wenn Sie Probleme mit abhängigen Basisklassen bekommen könnten, und "Sofortige" Suche, die Sie vontypename
und befreittemplate
.Unbekannte Spezialisierungen
Im Code von
D
ist der Nametypename D::questionable_type
kein Mitglied der aktuellen Instanziierung. Stattdessen kennzeichnet die Sprache es als Mitglied einer unbekannten Spezialisierung . Dies ist insbesondere dann immer der Fall, wenn Sie dies tun,DependentTypeName::Foo
oderDependentTypedName->Foo
entweder ist der abhängige Typ nicht die aktuelle Instanziierung (in diesem Fall kann der Compiler aufgeben und sagen "Wir werden später nachsehen, wasFoo
ist") oder es ist die aktuelle Instanziierung und die Der Name wurde darin oder in seinen nicht abhängigen Basisklassen nicht gefunden, und es gibt auch abhängige Basisklassen.Stellen Sie sich vor, was passiert, wenn wir eine Mitgliedsfunktion
h
in der oben definiertenA
Klassenvorlage habenIn C ++ 03 konnte die Sprache diesen Fehler abfangen, da es niemals eine gültige Möglichkeit zum Instanziieren geben konnte
A<T>::h
(unabhängig davon, welches Argument Sie angebenT
). In C ++ 11 hat die Sprache jetzt eine weitere Prüfung, um den Compilern mehr Gründe für die Implementierung dieser Regel zu geben. DaA
keine abhängigen Basisklassen hat, undA
erklärt , kein Mitgliedquestionable_type
der NameA<T>::questionable_type
ist weder ein Mitglied der aktuellen Instanziierung nochein 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, bei dem das Qualifikationsmerkmal die aktuelle Instanziierung ist, weder Mitglied einer unbekannten Spezialisierung noch Mitglied der aktuellen Instanziierung ist (jedoch) muss dieser Verstoß noch nicht diagnostiziert werden).Beispiele und Wissenswertes
Sie können dieses Wissen anhand dieser Antwort ausprobieren und anhand eines Beispiels aus der Praxis feststellen, ob die obigen Definitionen für Sie sinnvoll sind (sie werden in dieser Antwort etwas weniger detailliert wiederholt).
Die C ++ 11-Regeln machen den folgenden gültigen C ++ 03-Code falsch (was nicht vom C ++ - Komitee beabsichtigt war, aber wahrscheinlich nicht behoben wird)
Dieser gültige C ++ 03-Code wird
this->f
zumA::f
Zeitpunkt der Instanziierung gebunden und alles ist in Ordnung. C ++ 11 bindet es jedoch sofort anB::f
und erfordert beim Instanziieren eine doppelte Überprüfung, um zu überprüfen, ob die Suche noch übereinstimmt. Beim InstanziierenC<A>::g
gilt jedoch die Dominanzregel , undA::f
stattdessen wird nachgeschlagen .quelle
Was ist der Zweck von
typename
undtemplate
?typename
undtemplate
können unter anderen Umständen als beim Deklarieren einer Vorlage verwendet werden.Es gibt bestimmte Kontexte in 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 Template-Parameter ab .
Wir beziehen uns auf solche Namen, bei denen es zu Mehrdeutigkeiten bei der Interpretation kommen kann, wie: " 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 WÖRTER
Versuchen Sie, sich selbst, einem Freund oder vielleicht Ihrer Katze zu erklären, was in der folgenden Funktionsvorlage vor sich geht. Was passiert in der mit ( A ) gekennzeichneten Aussage ?
Es ist möglicherweise nicht so einfach wie man denkt, insbesondere hängt das Ergebnis der Auswertung von ( A ) stark von der Definition des als Template-Parameter übergebenen Typs ab
T
.Verschiedene
T
s können die Semantik drastisch verändern.Die zwei verschiedenen Szenarien :
Wenn wir die Funktionsvorlage wie in ( C ) mit Typ X instanziieren , haben wir eine Deklaration eines Zeigers auf int mit dem Namen x , aber;
Wenn wir die Vorlage wie in ( D ) mit dem Typ Y instanziieren , 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 eine Umsetzung zu verhindern , dass potenziell vor bösen Überraschungen leiden, die Standard - Mandate , dass wir aussortieren die Mehrdeutigkeit eines abhängigen Namen durch ausdrücklich die Absicht überall besagen , möchten wir den Namen entweder als zur Behandlung von Typ-Namen oder einem template- id .
Wenn nichts angegeben ist, wird der abhängige Name entweder als Variable oder als Funktion betrachtet.
WIE BEHINDEREN SIE ABHÄNGIGE NAMEN ?
Wenn dies ein Hollywood-Film wäre, wären abhängige Namen die Krankheit, die sich durch Körperkontakt ausbreitet und sofort den Wirt betrifft, 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 Name, der direkt oder indirekt von einem Vorlagenparameter abhängt .
Wir haben vier abhängige Namen im obigen Snippet:
SomeTrait<T>
, einschließlichT
und;SomeTrait<T>
und ab.SomeTrait<T>
und abhängt ;SomeTrait<T>
.Keine der Anweisungen ( 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 erhalten, müssen wir dem Compiler explizit mitteilen, dass wir einen Typ in ( E ), eine Vorlagen-ID und einen Typ in ( F ) sowie eine Vorlagen-ID in ( G ) erwarten .Jedes Mal, wenn ein Name einen Typ bezeichnet, müssen alle beteiligten Namen entweder Typnamen oder Namespaces sein . In diesem Sinne ist es leicht zu erkennen, dass wir uns
typename
am Anfang unseres vollqualifizierten Namens bewerben .template
Dies ist jedoch in dieser Hinsicht anders, da es keine Möglichkeit gibt, zu einer Schlussfolgerung zu gelangen, wie z. "Oh, das ist eine Vorlage, dann muss diese andere Sache auch eine Vorlage sein" . Dies bedeutet, dass wir unstemplate
direkt vor jedem Namen bewerben , den wir als solchen behandeln möchten.KANN ICH NUR DIE SCHLÜSSELWÖRTER VOR JEDEM NAMEN STEHEN ?
Die Regeln im Standard besagen, dass Sie die Schlüsselwörter anwenden dürfen, solange Sie sich mit einem qualifizierten Namen ( K ) befassen. Wenn der Name jedoch nicht qualifiziert ist, ist die Anwendung fehlerhaft ( L ).
Hinweis : Die Anwendung
typename
odertemplate
in einem Kontext, in dem dies nicht erforderlich ist, wird nicht als bewährte Methode angesehen. Nur weil du etwas tun kannst, heißt das nicht, dass du es tun solltest.Zusätzlich gibt es Kontexte , in denen
typename
undtemplate
sind ausdrücklich nicht zugelassen:Bei der Angabe der Basen, von denen eine Klasse erbt
Jeder Name in einer abgeleiteten Klasse eine schriftliche Basis-Bezeichner-Liste bereits als behandelt Typname , explizite Angabe
typename
ist sowohl schlecht ausgebildet und redundant.Wenn die Vorlagen-ID diejenige ist, auf die in der using-Direktive einer abgeleiteten Klasse verwiesen wird
quelle
Ich bin mir jedoch nicht sicher, ob die Implementierung von inUnion korrekt ist. Wenn ich das richtig verstehe, sollte diese Klasse nicht instanziiert werden, daher wird die Registerkarte "Fail" niemals automatisch fehlschlagen. Vielleicht ist es besser anzugeben, ob der Typ in der Union ist oder nicht, mit einem einfachen booleschen Wert.
PS: Schauen Sie sich Boost :: Variant an
PS2: Sehen Sie sich Typelisten an , insbesondere in Andrei Alexandrescus Buch: Modernes C ++ - Design
quelle
Diese Antwort soll eher kurz und bündig sein, um (einen Teil) der betitelten Frage zu beantworten. Wenn Sie eine ausführlichere Antwort wünschen, die erklärt, warum Sie sie dort ablegen müssen, klicken Sie bitte hier .
Die allgemeine Regel für das Einfügen des
typename
Schlüsselworts lautet meistens, wenn Sie einen Vorlagenparameter verwenden und auf einen verschachteltentypedef
oder verwendenden Alias zugreifen möchten , zum Beispiel:Beachten Sie, dass dies auch für Metafunktionen oder Dinge gilt, die generische Vorlagenparameter verwenden. Wenn es sich bei dem angegebenen Vorlagenparameter jedoch um einen expliziten Typ handelt, müssen Sie beispielsweise Folgendes nicht angeben
typename
:Die allgemeinen Regeln zum Hinzufügen des
template
Qualifizierers sind größtenteils ähnlich, außer dass sie normalerweise Vorlagenfunktionen (statisch oder anderweitig) einer Struktur / Klasse beinhalten, die selbst als Vorlage dient, zum Beispiel:Angesichts dieser Struktur und Funktion:
Der Versuch,
t.get<int>()
von innerhalb der Funktion zuzugreifen, führt zu einem Fehler:In diesem Zusammenhang benötigen Sie also das
template
Schlüsselwort im Voraus und nennen es folgendermaßen:t.template get<int>()
Auf diese Weise analysiert der Compiler dies nicht richtig
t.get < int
.quelle
Ich stelle JLBorges 'ausgezeichnete Antwort auf eine ähnliche Frage wörtlich von cplusplus.com, da dies die prägnanteste Erklärung ist, die ich zu diesem Thema gelesen habe.
Zusammenfassung
Verwenden Sie das Schlüsselwort Typname nur in Vorlagendeklarationen und -definitionen, sofern Sie einen qualifizierten Namen haben, der auf einen Typ verweist und von einem Vorlagenparameter abhängt.
quelle