Abhängige Namen
Innerhalb der Definition einer Vorlage (sowohl Klassenvorlage als auch Funktionsvorlage) kann die Bedeutung einiger Konstrukte von einer Instanziierung zur anderen abweichen. Insbesondere können Typen und Ausdrücke von Typen von Typvorlagenparametern und Werten von Nicht-Typ-Vorlagenparametern abhängen.
template<typename T> struct X : B<T> // “B<T>” is dependent on T { typename T::A* pa; // “T::A” is dependent on T // (see below for the meaning of this use of “typename”) void f(B<T>* pb) { static int i = B<T>::i; // “B<T>::i” is dependent on T pb->j++; // “pb->j” is dependent on T } };
Namens-Suche und Bindung sind für abhängige Namen und nicht-abhängige Namen unterschiedlich.
[bearbeiten] Bindungsregeln
Nicht-abhängige Namen werden am Punkt der Vorlagendefinition gesucht und gebunden. Diese Bindung gilt auch dann, wenn am Punkt der Vorlageninstanziierung eine bessere Übereinstimmung vorliegt.
Wenn sich die Bedeutung eines nicht-abhängigen Namens zwischen dem Definitionskontext und dem Punkt der Instanziierung einer Spezialisierung der Vorlage ändert, ist das Programm fehlerhaft, keine Diagnose erforderlich. Dies ist in folgenden Situationen möglich:
- ein in einem nicht-abhängigen Namen verwendeter Typ ist am Punkt der Definition unvollständig, aber am Punkt der Instanziierung vollständig
|
(seit C++17) |
- eine Instanziierung verwendet ein Standardargument oder ein Standard-Vorlagenargument, das am Punkt der Definition noch nicht definiert war
- ein konstanter Ausdruck verwendet am Punkt der Instanziierung den Wert eines const-Objekts vom ganzzahligen Typ oder vom Typ einer unbenannten Aufzählung, den Wert eines constexpr-Objekts, den Wert einer Referenz oder die Definition einer constexpr-Funktion(seit C++11), und dieses Objekt/Referenz/Funktion(seit C++11) war am Punkt der Definition nicht definiert
- die Vorlage verwendet am Punkt der Instanziierung eine nicht-abhängige Klassenvorlagenspezialisierung oder Variablentemplatesspezialisierung(seit C++14), und diese verwendete Vorlage ist entweder aus einer partiellen Spezialisierung instanziiert, die am Punkt der Definition nicht definiert war, oder sie benennt eine explizite Spezialisierung, die am Punkt der Definition nicht deklariert war
Die Bindung von abhängigen Namen wird aufgeschoben, bis die Suche stattfindet.
[bearbeiten] Suchregeln
Die Suche eines abhängigen Namens, der in einer Vorlage verwendet wird, wird aufgeschoben, bis die Vorlagenargumente bekannt sind, und dann
- untersucht die Nicht-ADL-Suche Funktionsdeklarationen mit externer Verknüpfung, die vom Kontext der Vorlagendefinition aus sichtbar sind.
- ADL untersucht Funktionsdeklarationen mit externer Verknüpfung, die entweder vom Kontext der Vorlagendefinition oder vom Kontext der Vorlageninstanziierung aus sichtbar sind.
(Mit anderen Worten, das Hinzufügen einer neuen Funktionsdeklaration nach der Vorlagendefinition macht sie nicht sichtbar, außer über ADL).
Zweck dieser Regel ist es, Verstöße gegen die ODR für Vorlageninstanziierungen zu verhindern.
// an external library namespace E { template<typename T> void writeObject(const T& t) { std::cout << "Value = " << t << '\n'; } } // translation unit 1: // Programmer 1 wants to allow E::writeObject to work with vector<int> namespace P1 { std::ostream& operator<<(std::ostream& os, const std::vector<int>& v) { for (int n : v) os << n << ' '; return os; } void doSomething() { std::vector<int> v; E::writeObject(v); // Error: will not find P1::operator<< } } // translation unit 2: // Programmer 2 wants to allow E::writeObject to work with vector<int> namespace P2 { std::ostream& operator<<(std::ostream& os, const std::vector<int>& v) { for (int n : v) os << n << ':'; return os << "[]"; } void doSomethingElse() { std::vector<int> v; E::writeObject(v); // Error: will not find P2::operator<< } }
Im obigen Beispiel würde, wenn die Nicht-ADL-Suche für operator<< aus dem Instanziierungskontext erlaubt wäre, die Instanziierung von E::writeObject<vector<int>> zwei verschiedene Definitionen haben: eine, die P1::operator<< verwendet, und eine, die P2::operator<< verwendet. Ein solcher ODR-Verstoß wird möglicherweise vom Linker nicht erkannt, was dazu führt, dass entweder die eine oder die andere in beiden Instanzen verwendet wird.
Um ADL in einem benutzerdefinierten Namensraum zu untersuchen, sollte entweder std::vector durch eine benutzerdefinierte Klasse ersetzt werden oder ihr Elementtyp sollte eine benutzerdefinierte Klasse sein.
namespace P1 { // if C is a class defined in the P1 namespace std::ostream& operator<<(std::ostream& os, const std::vector<C>& v) { for (C n : v) os << n; return os; } void doSomething() { std::vector<C> v; E::writeObject(v); // OK: instantiates writeObject(std::vector<P1::C>) // which finds P1::operator<< via ADL } }
Hinweis: Diese Regel macht das Überladen von Operatoren für Standardbibliothekstypen unpraktisch.
#include <iostream> #include <iterator> #include <utility> #include <vector> // Bad idea: operator in global namespace, but its arguments are in std:: std::ostream& operator<<(std::ostream& os, std::pair<int, double> p) { return os << p.first << ',' << p.second; } int main() { typedef std::pair<int, double> elem_t; std::vector<elem_t> v(10); std::cout << v[0] << '\n'; // OK, ordinary lookup finds ::operator<< std::copy(v.begin(), v.end(), std::ostream_iterator<elem_t>(std::cout, " ")); // Error: both ordinary lookup from the point of definition of // std::ostream_iterator and ADL will only consider the std namespace, // and will find many overloads of std::operator<<, so the lookup will be done. // Overload resolution will then fail to find operator<< for elem_t // in the set found by the lookup. }
Hinweis: Eine eingeschränkte Suche (aber keine Bindung) von abhängigen Namen findet auch zum Zeitpunkt der Vorlagendefinition statt, um sie von nicht-abhängigen Namen zu unterscheiden und auch um festzustellen, ob es sich um Member der aktuellen Instanziierung oder um Member einer unbekannten Spezialisierung handelt. Die durch diese Suche gewonnenen Informationen können zur Fehlererkennung verwendet werden, siehe unten.
[bearbeiten] Abhängige Typen
Die folgenden Typen sind abhängige Typen:
- Vorlagenparameter
- ein Mitglied einer unbekannten Spezialisierung (siehe unten)
- eine verschachtelte Klasse/Aufzählung, die ein abhängiges Mitglied einer unbekannten Spezialisierung ist (siehe unten)
- eine cv-qualifizierte Version eines abhängigen Typs
- ein aus einem abhängigen Typ konstruierter zusammengesetzter Typ
- ein Array-Typ, dessen Elementtyp abhängig ist oder dessen Grenze (falls vorhanden) wertabhängig ist
|
(seit C++11) |
- ein Funktionstyp, dessen Exception-Spezifikation wertabhängig ist
- ein template-id, bei dem entweder
- der Vorlagenname ein Vorlagenparameter ist oder
- jedes der Vorlagenargumente typabhängig oder wertabhängig ist, oder eine Pack-Erweiterung ist(seit C++11) (auch wenn das template-id ohne seine Argumentliste verwendet wird, als injected-class-name)
Das Ergebnis von decltype, angewendet auf einen typabhängigen Ausdruck, ist ein eindeutiger abhängiger Typ. Zwei solche Ergebnisse beziehen sich nur dann auf denselben Typ, wenn ihre Ausdrücke äquivalent sind. |
(seit C++11) |
Der Pack-Index-Spezifizierer, angewendet auf einen typabhängigen konstanten Ausdruck, ist ein eindeutiger abhängiger Typ. Zwei solche Pack-Index-Spezifizierer beziehen sich nur dann auf denselben Typ, wenn ihre konstanten Ausdrücke äquivalent sind. Andernfalls beziehen sich zwei solche Pack-Index-Spezifizierer nur dann auf denselben Typ, wenn ihre Indizes denselben Wert haben. |
(seit C++26) |
Hinweis: Ein typedef-Mitglied einer aktuellen Instanziierung ist nur dann abhängig, wenn der Typ, auf den es verweist, es ist.
[bearbeiten] Typabhängige Ausdrücke
Die folgenden Ausdrücke sind typabhängig:
- ein Ausdruck, dessen Unterausdruck ein typabhängiger Ausdruck ist
- this, wenn die Klasse ein abhängiger Typ ist.
- ein Bezeichnerausdruck, der kein Concept-ID ist und(seit C++20)
- einen Bezeichner enthält, für den die Namenssuche mindestens eine abhängige Deklaration findet
- enthält ein abhängiges template-id
|
(seit C++11) |
- enthält den Namen einer Konstruktorfunktion zu einem abhängigen Typ
- enthält einen verschachtelten Namensspezifizierer oder ein qualifiziertes ID, das ein Mitglied einer unbekannten Spezialisierung ist
- benennt ein abhängiges Mitglied der aktuellen Instanziierung, das ein statisches Datenmitglied vom Typ "Array unbekannter Größe" ist
|
(seit C++14) |
|
(seit C++17) |
|
(seit C++26) |
- jede Cast-Operation zu einem abhängigen Typ
- new-Ausdruck, der ein Objekt eines abhängigen Typs erzeugt
- Mitgliedszugriffsausdruck, der sich auf ein Mitglied der aktuellen Instanziierung bezieht, dessen Typ abhängig ist
- Mitgliedszugriffsausdruck, der sich auf ein Mitglied einer unbekannten Spezialisierung bezieht
| (seit C++17) |
|
(seit C++26) |
Die folgenden Ausdrücke sind niemals typabhängig, da die Typen dieser Ausdrücke nicht sein können.
| (seit C++11) |
| (seit C++20) |
[bearbeiten] Wertabhängige Ausdrücke
Die folgenden Ausdrücke sind wertabhängig:
- ein Ausdruck, der in einem Kontext verwendet wird, in dem ein konstanter Ausdruck erforderlich ist, und dessen Unterausdruck wertabhängig ist
- ein Bezeichnerausdruck, der eine der folgenden Bedingungen erfüllt:
|
(seit C++20) |
- Es ist typabhängig.
- Es ist der Name eines Nicht-Typ-Vorlagenparameters.
- Es benennt ein statisches Datenmitglied, das ein abhängiges Mitglied der aktuellen Instanziierung ist und nicht initialisiert wurde.
- Es benennt eine statische Mitgliedsfunktion, die ein abhängiges Mitglied der aktuellen Instanziierung ist.
- Es ist eine Konstante mit einem Ganzzahl- oder Aufzählungstyp(bis C++11)Literal(seit C++11), initialisiert aus einem wertabhängigen Ausdruck.
- die folgenden Ausdrücke, bei denen der Operand ein typabhängiger Ausdruck ist
| (seit C++11) |
- die folgenden Ausdrücke, bei denen der Operand ein abhängiges Typ-ID ist
- die folgenden Ausdrücke, bei denen der Zieltyp abhängig ist oder der Operand ein typabhängiger Ausdruck ist
- funktionsartiger Cast-Ausdruck, bei dem der Zieltyp abhängig ist oder ein wertabhängiger Ausdruck in Klammern stehtoder geschweifte Klammern(seit C++11)
|
(seit C++11) |
| (seit C++17) |
- Adress-Operator, wenn das Argument ein qualifiziertes Bezeichner ist, das ein abhängiges Mitglied der aktuellen Instanziierung benennt
- Adress-Operator, wenn das Argument ein beliebiger Ausdruck ist, der, ausgewertet als ein Kern-konstanter Ausdruck, auf eine vorlagengebundene Entität verweist, die ein Objekt mit statischer oder Thread-Speicher(seit C++11) Dauer oder eine Mitgliedsfunktion ist.
[bearbeiten] Abhängige Namen
| Dieser Abschnitt ist unvollständig Grund: Die Einleitung von [temp.dep], die fehlt (Bezeichnerausdruck gefolgt von einer Klammerliste... |
| Dieser Abschnitt ist unvollständig Grund: Umformulierung, um es vielleicht klarer (oder zumindest weniger einschüchternd) zu machen, und während wir dabei sind, CWG-Problem 591 anzuwenden |
[bearbeiten] Aktuelle Instanziierung
Innerhalb der Definition einer Klassenvorlage (einschließlich ihrer Mitgliedsfunktionen und verschachtelten Klassen) können einige Namen so abgeleitet werden, dass sie sich auf die aktuelle Instanziierung beziehen. Dies ermöglicht die Erkennung bestimmter Fehler am Punkt der Definition statt der Instanziierung und beseitigt die Anforderung für die Disambiguatoren typename und template für abhängige Namen, siehe unten.
Nur die folgenden Namen können sich auf die aktuelle Instanziierung beziehen:
- in der Definition einer Klassenvorlage, einer verschachtelten Klasse einer Klassenvorlage, eines Mitglieds einer Klassenvorlage oder eines Mitglieds einer verschachtelten Klasse einer Klassenvorlage
- der injizierte Klassenname der Klassenvorlage oder der verschachtelten Klasse
- in der Definition einer primären Klassenvorlage oder eines Mitglieds einer primären Klassenvorlage
- der Name der Klassenvorlage gefolgt von einer Vorlagenargumentliste (oder einer äquivalenten Aliasvorlagenspezialisierung) für die primäre Vorlage, wobei jedes Argument äquivalent zu seinem entsprechenden Parameter ist (wie unten definiert).
- in der Definition einer verschachtelten Klasse einer Klassenvorlage
- der Name der verschachtelten Klasse, der als Mitglied der aktuellen Instanziierung verwendet wird
- in der Definition einer partiellen Spezialisierung einer Klassenvorlage oder eines Mitglieds einer partiellen Spezialisierung einer Klassenvorlage
- der Name der Klassenvorlage gefolgt von einer Vorlagenargumentliste für die partielle Spezialisierung, wobei jedes Argument zu seinem entsprechenden Parameter äquivalent ist
- in der Definition einer vorlagengestützten Funktion
- der Name einer lokalen Klasse
Ein Vorlagenargument ist äquivalent zu einem Vorlagenparameter, wenn
- für einen Typ-Parameter das Vorlagenargument denselben Typ wie der Vorlagenparameter bezeichnet.
- für einen Nicht-Typ-Parameter das Vorlagenargument ein Bezeichner ist, der eine Variable benennt, die dem Vorlagenparameter äquivalent ist. Eine Variable ist einem Vorlagenparameter äquivalent, wenn
- sie denselben Typ wie der Vorlagenparameter hat (unter Ignorierung der cv-Qualifizierung) und
- ihr Initialisierer aus einem einzigen Bezeichner besteht, der den Vorlagenparameter oder rekursiv eine solche Variable benennt.
template<class T> class A { A* p1; // A is the current instantiation A<T>* p2; // A<T> is the current instantiation ::A<T>* p4; // ::A<T> is the current instantiation A<T*> p3; // A<T*> is not the current instantiation class B { B* p1; // B is the current instantiation A<T>::B* p2; // A<T>::B is the current instantiation typename A<T*>::B* p3; // A<T*>::B is not the current instantiation }; }; template<class T> class A<T*> { A<T*>* p1; // A<T*> is the current instantiation A<T>* p2; // A<T> is not the current instantiation }; template<int I> struct B { static const int my_I = I; static const int my_I2 = I + 0; static const int my_I3 = my_I; static const long my_I4 = I; static const int my_I5 = (I); B<my_I>* b1; // B<my_I> is the current instantiation: // my_I has the same type as I, // and it is initialized with only I B<my_I2>* b2; // B<my_I2> is not the current instantiation: // I + 0 is not a single identifier B<my_I3>* b3; // B<my_I3> is the current instantiation: // my_I3 has the same type as I, // and it is initialized with only my_I (which is equivalent to I) B<my_I4>* b4; // B<my_I4> is not the current instantiation: // the type of my_I4 (long) is not the same as the type of I (int) B<my_I5>* b5; // B<my_I5> is not the current instantiation: // (I) is not a single identifier };
Beachten Sie, dass eine Basisklasse die aktuelle Instanziierung sein kann, wenn eine verschachtelte Klasse von ihrer umschließenden Klassenvorlage erbt. Basisklassen, die abhängige Typen sind, aber nicht die aktuelle Instanziierung sind, sind abhängige Basisklassen.
template<class T> struct A { typedef int M; struct B { typedef void M; struct C; }; }; template<class T> struct A<T>::B::C : A<T> { M m; // OK, A<T>::M };
Ein Name wird als Mitglied der aktuellen Instanziierung klassifiziert, wenn er
- ein nicht-qualifizierter Name ist, der durch nicht-qualifizierte Suche in der aktuellen Instanziierung oder in ihrer nicht-abhängigen Basis gefunden wird.
- Qualifizierter Name, wenn der Qualifizierer (der Name links von
::) die aktuelle Instanziierung benennt und die Suche den Namen in der aktuellen Instanziierung oder in ihrer nicht-abhängigen Basis findet. - ein Name, der in einem Klassenmitglieds-Zugriffsausdruck verwendet wird (y in x.y oder xp->y), wobei der Objekt-Ausdruck (x oder *xp) die aktuelle Instanziierung ist und die Suche den Namen in der aktuellen Instanziierung oder in ihrer nicht-abhängigen Basis findet.
template<class T> class A { static const int i = 5; int n1[i]; // i refers to a member of the current instantiation int n2[A::i]; // A::i refers to a member of the current instantiation int n3[A<T>::i]; // A<T>::i refers to a member of the current instantiation int f(); }; template<class T> int A<T>::f() { return i; // i refers to a member of the current instantiation }
Mitglieder der aktuellen Instanziierung können sowohl abhängig als auch nicht-abhängig sein.
Wenn die Suche nach einem Mitglied der aktuellen Instanziierung zwischen dem Punkt der Instanziierung und dem Punkt der Definition ein anderes Ergebnis liefert, ist die Suche mehrdeutig. Beachten Sie jedoch, dass, wenn ein Mitgliedsname verwendet wird, er nicht automatisch in einen Klassenmitglieds-Zugriffsausdruck umgewandelt wird; nur explizite Mitglieds-Zugriffsausdrücke kennzeichnen Mitglieder der aktuellen Instanziierung.
struct A { int m; }; struct B { int m; }; template<typename T> struct C : A, T { int f() { return this->m; } // finds A::m in the template definition context int g() { return m; } // finds A::m in the template definition context }; template int C<B>::f(); // error: finds both A::m and B::m template int C<B>::g(); // OK: transformation to class member access syntax // does not occur in the template definition context
[bearbeiten] Unbekannte Spezialisierungen
Innerhalb einer Vorlagendefinition werden bestimmte Namen einer unbekannten Spezialisierung zugeordnet, insbesondere:
- Qualifizierter Name, wenn ein Name links von
::ein abhängiger Typ ist, der kein Mitglied der aktuellen Instanziierung ist. - Qualifizierter Name, dessen Qualifizierer die aktuelle Instanziierung ist, und der Name wird weder in der aktuellen Instanziierung noch in einer ihrer nicht-abhängigen Basisklassen gefunden, und es gibt eine abhängige Basisklasse.
- Ein Mitgliedsname in einem Klassenmitglieds-Zugriffsausdruck (das y in x.y oder xp->y), wenn der Typ des Objekt-Ausdrucks (x oder *xp) ein abhängiger Typ ist und nicht die aktuelle Instanziierung ist.
- Ein Mitgliedsname in einem Klassenmitglieds-Zugriffsausdruck (das y in x.y oder xp->y), wenn der Typ des Objekt-Ausdrucks (x oder *xp) die aktuelle Instanziierung ist, und der Name weder in der aktuellen Instanziierung noch in einer ihrer nicht-abhängigen Basisklassen gefunden wird, und es eine abhängige Basisklasse gibt.
template<typename T> struct Base {}; template<typename T> struct Derived : Base<T> { void f() { // Derived<T> refers to current instantiation // there is no “unknown_type” in the current instantiation // but there is a dependent base (Base<T>) // Therefore, “unknown_type” is a member of unknown specialization typename Derived<T>::unknown_type z; } }; template<> struct Base<int> // this specialization provides it { typedef int unknown_type; };
Diese Klassifizierung ermöglicht die Erkennung folgender Fehler am Punkt der Vorlagendefinition (statt der Instanziierung):
- Wenn eine Vorlagendefinition einen qualifizierten Namen hat, bei dem der Qualifizierer auf die aktuelle Instanziierung verweist und der Name weder ein Mitglied der aktuellen Instanziierung noch ein Mitglied einer unbekannten Spezialisierung ist, ist das Programm fehlerhaft (keine Diagnose erforderlich), auch wenn die Vorlage nie instanziiert wird.
template<class T> class A { typedef int type; void f() { A<T>::type i; // OK: “type” is a member of the current instantiation typename A<T>::other j; // Error: // “other” is not a member of the current instantiation // and it is not a member of an unknown specialization // because A<T> (which names the current instantiation), // has no dependent bases for “other” to hide in. } };
- Wenn eine Vorlagendefinition einen Mitglieds-Zugriffsausdruck hat, bei dem der Objekt-Ausdruck die aktuelle Instanziierung ist, der Name aber weder ein Mitglied der aktuellen Instanziierung noch ein Mitglied einer unbekannten Spezialisierung ist, ist das Programm fehlerhaft, auch wenn die Vorlage nie instanziiert wird.
Mitglieder einer unbekannten Spezialisierung sind immer abhängig und werden am Punkt der Instanziierung wie alle abhängigen Namen gesucht und gebunden (siehe oben).
[bearbeiten] Der typename-Disambiguator für abhängige Namen
In einer Deklaration oder Definition einer Vorlage, einschließlich Aliasvorlagen, wird ein Name, der kein Mitglied der aktuellen Instanziierung ist und von einem Vorlagenparameter abhängt, nicht als Typ betrachtet, es sei denn, das Schlüsselwort typename wird verwendet oder es wurde bereits als Typname etabliert, z. B. mit einer typedef-Deklaration oder durch Verwendung zur Benennung einer Basisklasse.
#include <iostream> #include <vector> int p = 1; template<typename T> void foo(const std::vector<T> &v) { // std::vector<T>::const_iterator is a dependent name, typename std::vector<T>::const_iterator it = v.begin(); // without “typename”, the following is parsed as multiplication // of the type-dependent data member “const_iterator” // and some variable “p”. Since there is a global “p” visible // at this point, this template definition compiles. std::vector<T>::const_iterator* p; typedef typename std::vector<T>::const_iterator iter_t; iter_t * p2; // “iter_t” is a dependent name, but it is known to be a type name } template<typename T> struct S { typedef int value_t; // member of current instantiation void f() { S<T>::value_t n{}; // S<T> is dependent, but “typename” not needed std::cout << n << '\n'; } }; int main() { std::vector<int> v; foo(v); // template instantiation fails: there is no member variable // called “const_iterator” in the type std::vector<int> S<int>().f(); }
Das Schlüsselwort typename kann auf diese Weise nur vor qualifizierten Namen verwendet werden (z. B. T::x), aber die Namen müssen nicht abhängig sein.
Eine normale qualifizierte Namenssuche wird für den Bezeichner durchgeführt, dem typename vorangestellt ist. Im Gegensatz zum Fall mit elaborated type specifier ändern sich die Suchregeln trotz des Qualifizierers nicht.
struct A // A has a nested variable X and a nested type struct X { struct X {}; int X; }; struct B { struct X {}; // B has a nested type struct X }; template<class T> void f(T t) { typename T::X x; } void foo() { A a; B b; f(b); // OK: instantiates f<B>, T::X refers to B::X f(a); // error: cannot instantiate f<A>: // because qualified name lookup for A::X finds the data member }
Das Schlüsselwort typename kann auch außerhalb von Vorlagen verwendet werden.
#include <vector> int main() { // Both OK (after resolving CWG 382) typedef typename std::vector<int>::const_iterator iter_t; typename std::vector<int> v; }
|
In einigen Kontexten sind nur Typnamen gültig. In diesen Kontexten wird ein abhängiger qualifizierter Name angenommen, einen Typ zu benennen, und es ist kein typename erforderlich.
|
(seit C++20) |
[bearbeiten] Der template-Disambiguator für abhängige Namen
Ähnlich wird in einer Vorlagendefinition ein abhängiger Name, der kein Mitglied der aktuellen Instanziierung ist, nicht als Vorlagenname betrachtet, es sei denn, das Disambiguierungs-Schlüsselwort template wird verwendet oder es wurde bereits als Vorlagenname etabliert.
template<typename T> struct S { template<typename U> void foo() {} }; template<typename T> void bar() { S<T> s; s.foo<T>(); // error: < parsed as less than operator s.template foo<T>(); // OK }
Das Schlüsselwort template kann auf diese Weise nur nach den Operatoren :: (Scope-Auflösung), -> (Mitgliedszugriff über Zeiger) und . (Mitgliedszugriff) verwendet werden. Die folgenden sind gültige Beispiele:
- T::template foo<X>();
- s.template foo<X>();
- this->template foo<X>();
- typename T::template iterator<int>::value_type v;
Wie beim typename ist das Präfix template auch dann erlaubt, wenn der Name nicht abhängig ist oder die Verwendung nicht im Gültigkeitsbereich einer Vorlage erscheint.
Auch wenn der Name links von :: auf einen Namensraum verweist, ist der Vorlagen-Disambiguator erlaubt.
template<typename> struct S {}; ::template S<void> q; // allowed, but unnecessary
|
Aufgrund der Sonderregeln für die nicht-qualifizierte Namenssuche für Vorlagennamen in Mitgliedszugriffsausdrücken ist der Disambiguator unnötig, wenn ein nicht-abhängiger Vorlagenname in einem Mitgliedszugriffsausdruck (nach -> oder nach .) vorkommt und es eine Klassen- oder Aliasvorlage(seit C++11) mit demselben Namen gibt, die durch normale Suche im Kontext des Ausdrucks gefunden wird. Wenn jedoch die durch die Suche im Kontext der Klasse gefundene Vorlage von der im Kontext der Klasse gefundenen abweicht, ist das Programm fehlerhaft(bis C++11) template<int> struct A { int value; }; template<class T> void f(T t) { t.A<0>::value; // Ordinary lookup of A finds a class template. // A<0>::value names member of class A<0> // t.A < 0; // Error: “<” is treated as the start of template argument list } |
(bis C++23) |
[bearbeiten] Schlüsselwörter
[bearbeiten] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 206 | C++98 | Es war unklar, an welchem Punkt semantische Einschränkungen angewendet wurden, wenn ein Typ, der in einem nicht-abhängigen Namen verwendet wird, am Punkt der Vorlagendefinition unvollständig ist, aber am Punkt der Instanziierung vollständig ist. |
Das Programm ist fehlerhaft und keine Diagnose ist in diesem Fall erforderlich. |
| CWG 224 | C++98 | Die Definition von abhängigen Typen basierte auf der Form des Namens und nicht auf der Suche. |
Definition überarbeitet. |
| CWG 382 | C++98 | Der typename-Disambiguator war nur im Vorlagen-Gültigkeitsbereich erlaubt. | Auch außerhalb von Vorlagen erlaubt. |
| CWG 468 | C++98 | Der template-Disambiguator war nur im Vorlagen-Gültigkeitsbereich erlaubt. | Auch außerhalb von Vorlagen erlaubt. |
| CWG 502 | C++98 | Es war unklar, ob verschachtelte Aufzählungen als | abhängig wie verschachtelte Klassen betrachtet wurden. |
| CWG 1047 | C++98 | typeid-Ausdrücke waren nie wertabhängig. | Wertabhängig, wenn der Operand typabhängig ist. |
| CWG 1160 | C++98 | Es war unklar, ob ein Name sich auf die aktuelle Instanziierung bezieht, wenn ein template-id, das mit einer primären Vorlage oder einer partiellen Spezialisierung übereinstimmt, in der Definition eines Mitglieds der Vorlage erscheint. |
spezifiziert |
| CWG 1413 | C++98 | Nicht initialisierte statische Datenmitglieder, statische Mitgliedsfunktionen und Adressen von Mitgliedern von Klassenvorlagen waren nicht als wertabhängig aufgeführt. |
Aufgeführt. |
| CWG 1471 | C++98 | Ein verschachtelter Typ einer nicht-abhängigen Basis von der aktuellen Instanziierung war abhängig. |
Ist nicht abhängig. |
| CWG 1850 | C++98 | Die Liste der Fälle, in denen sich die Bedeutung zwischen dem Definitionskontext und dem Instanziierungspunkt ändern kann, war unvollständig. |
vollständig gemacht |
| CWG 1929 | C++98 | Es war unklar, ob der template-Disambiguator auf ein :: folgen kann, bei dem der Name links davon auf einen Namensraum verweist. |
erlaubt |
| CWG 2066 | C++98 | this war nie wertabhängig. | Es kann sein wertabhängig. |
| CWG 2100 | C++98 | Die Adresse eines statischen Datenmitglieds einer Klasse Vorlage war nicht als wertabhängig aufgeführt. |
Aufgeführt. |
| CWG 2109 | C++98 | Typabhängige Bezeichnerausdrücke waren möglicherweise nicht wertabhängig. | Sie sind immer wertabhängig. |
| CWG 2276 | C++98 | Ein Funktionstyp, dessen Exception-Spezifikation wertabhängig war, war kein abhängiger Typ. |
sie ist |
| CWG 2307 | C++98 | Ein geklammerter Nicht-Typ-Vorlagenparameter, der als Vorlagenargument verwendet wird, war äquivalent zu diesem Vorlagenparameter. |
Nicht mehr äquivalent. |
| CWG 2457 | C++11 | Ein Funktionstyp mit Funktionsparameter Pack war kein abhängiger Typ. |
sie ist |
| CWG 2785 | C++20 | requires-Ausdrücke könnten typabhängig sein. | Sie sind niemals typabhängig. |
| CWG 2905 | C++11 | Ein noexcept-Ausdruck war nur wertabhängig, wenn sein Operand wertabhängig ist. |
Er ist wertabhängig, wenn sein Operand beinhaltet einen Vorlagenparameter. |
| CWG 2936 | C++98 | Die Namen von lokalen Klassen von vorlagengestützten Funktionen gehörten nicht zur aktuellen Instanziierung. |
sie sind |