Namensräume
Varianten
Aktionen

Abhängige Namen

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 
 
 

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.

Inhalt

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

#include <iostream>
 
void g(double) { std::cout << "g(double)\n"; }
 
template<class T>
struct S
{
    void f() const
    {
        g(1); // “g” is a non-dependent name, bound now
    }
};
 
void g(int) { std::cout << "g(int)\n"; }
 
int main()
{
    g(1);  // calls g(int)
 
    S<int> s;
    s.f(); // calls g(double)
}

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
  • die Suche nach einem Namen in der Vorlagendefinition ergab eine using-Deklaration, aber die Suche im entsprechenden Gültigkeitsbereich der Instanziierung findet keine Deklarationen, da die using-Deklaration eine Pack-Erweiterung war und das entsprechende Pack leer ist
(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
  • ein Funktionstyp, dessen Parameter einen oder mehrere Funktions-Parameter-Packs enthalten
(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

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
  • enthält den speziellen Bezeichner __func__ (wenn eine umschließende Funktion eine Vorlage, ein nicht-Vorlagen-Mitglied einer Klassenvorlage oder ein generisches Lambda ist(seit C++14))
(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
  • enthält einen Bezeichner, für den die Namenssuche eine oder mehrere Deklarationen von Mitgliedsfunktionen der aktuellen Instanziierung findet, die mit Rücktypdederuktion deklariert sind
(seit C++14)
  • enthält einen Bezeichner, für den die Namenssuche eine strukturelle Bindungsdeklaration findet, deren Initialisierer typabhängig ist
  • enthält einen Bezeichner, für den die Namenssuche einen Nicht-Typ-Vorlagenparameter findet, dessen Typ den Platzhalter auto enthält
  • enthält einen Bezeichner, für den die Namenssuche eine Variable findet, die mit einem Typ deklariert ist, der einen Platzhaltertyp enthält (z. B. auto statisches Datenmitglied), wobei der Initialisierer typabhängig ist,
(seit C++17)
  • enthält einen Bezeichner, für den die Namenssuche ein Pack findet
(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:
  • Es ist ein Concept-ID und beliebige seiner Argumente sind abhängig.
(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

[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

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.

  • Ein qualifizierter Name, der in einem Typnamen erscheint, bei dem der kleinste umschließende Typname
(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

template, typename

[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