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