(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:
t * f;
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 Zeigers f
. Wenn es sich jedoch nicht um einen Typ handelt, handelt es sich um eine Multiplikation. Der C ++ Standard sagt also in Absatz (3/7):
Einige Namen bezeichnen Typen oder Vorlagen. Im Allgemeinen muss bei jedem Auftreten eines Namens festgestellt werden, ob dieser Name eine dieser Entitäten bezeichnet, bevor das darin enthaltene Programm weiter analysiert wird. Der Prozess, der dies feststellt, wird als Namenssuche bezeichnet.
Wie findet der Compiler heraus, worauf sich ein Name t::x
bezieht, wenn er t
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:
Warten wir, bis der Benutzer die Vorlage instanziiert, und finden Sie später die wahre Bedeutung von heraus t::x * f;
.
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, damit typename
der Compiler ihn auf eine bestimmte Weise analysieren kann. Der Standard sagt bei (14.6 / 2):
Es wird angenommen, dass ein Name, der in einer Vorlagendeklaration oder -definition verwendet wird und von einem Vorlagenparameter abhängig ist, keinen Typ benennt, es sei denn, die entsprechende Namenssuche findet einen Typnamen oder der Name wird durch das Schlüsselwort Typname qualifiziert.
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 mit T *f;
, wenn T
es sich um einen Typvorlagenparameter handelt. Aber t::x * f;
um eine Erklärung zu sein, muss sie wie folgt geschrieben sein typename 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:
// 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 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:
boost::function< int() > f;
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 vor 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 Operator kleiner als zum Vergleichen boost::function
mit Null ( int()
) und verwendet dann den Operator größer als zum Vergleichen des Ergebnisses bool
mit f
. Wie Sie vielleicht wissen, handelt es sich boost::function
im wirklichen Leben um eine Vorlage, sodass der Compiler weiß (14.2 / 3):
Nachdem die Namenssuche (3.4) festgestellt hat, dass ein Name ein Vorlagenname ist. Wenn auf diesen Namen ein <folgt, wird das <immer als Anfang einer Vorlagenargumentliste und niemals als Name gefolgt von dem weniger- verwendet. als Betreiber.
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üssen template
unmittelbar vor dem Vorlagennamen einfügen , wie von angegeben 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 .
in einem Klassenmitgliedszugriff auftreten. Dort müssen Sie auch das Schlüsselwort einfügen:
this->template f<int>(); // call a function template
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:
- Abhängige Typen (z. B. ein Typvorlagenparameter
T
)
- Wertabhängige Ausdrücke (z. B. ein nicht typisierter Vorlagenparameter
N
)
- Typabhängige Ausdrücke (z. B. Umwandlung in einen Typvorlagenparameter
(T)0
)
Die meisten Regeln sind intuitiv und rekursiv aufgebaut: Beispielsweise ist ein Typ, der als T[N]
abhängiger Typ konstruiert ist, ein N
wertabhängiger Ausdruck oder T
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 Name ist eine Verwendung eines Bezeichners (2.11), einer Operatorfunktions-ID (13.5), einer Konvertierungsfunktions-ID (12.3.2) oder einer Vorlagen-ID (14.2), die eine Entität oder Bezeichnung (6.6.4, 6.1)
Ein Bezeichner ist nur eine einfache Folge von Zeichen / Ziffern, während die nächsten beiden die operator +
und operator type
-Form sind. Die letzte Form ist template-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 Name N
. 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 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;
};
// ...
};
Das 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 wird
typename t::template iterator<int>::value_type v;
In 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:
template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };
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.
template <typename T>
struct derive_from_Has_type : SomeBase<T> {
using SomeBase<T>::template type; // error
using typename SomeBase<T>::type; // typename *is* allowed
};