[c++] Wo und warum muss ich die Schlüsselwörter "template" und "typename" eingeben?



Answers

C ++ 11

Problem

Während die Regeln in C ++ 03 darüber, wann Sie typename und template benötigen, größtenteils vernünftig sind, gibt es einen lästigen Nachteil seiner Formulierung

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

Wie man sehen kann, brauchen wir das Disambiguierungs-Schlüsselwort, selbst wenn der Compiler selbst herausfinden könnte, dass A::result_type nur int (und daher ein Typ ist), und this->g kann nur die später deklarierte this->g Vorlage g (Selbst wenn A sich irgendwo explizit spezialisiert hat, würde das den Code innerhalb dieser Vorlage nicht beeinflussen, so dass seine Bedeutung nicht durch eine spätere Spezialisierung von A !).

Aktuelle Instanziierung

Um die Situation zu verbessern, wird in C ++ 11 die Sprache verfolgt, wenn sich ein Typ auf die umschließende Vorlage bezieht. Um das zu wissen, muss der Typ unter Verwendung einer bestimmten Form des Namens gebildet worden sein, der sein eigener Name ist (oben A , A<T> , ::A<T> ). Ein Typ, auf den sich ein solcher Name bezieht, ist bekanntlich die aktuelle Instanziierung . Wenn der Typ, aus dem der Name gebildet wird, ein Mitglied / eine geschachtelte Klasse ist (dann sind A::NestedClass und A beide aktuelle Instanziierungen) möglicherweise mehrere Typen, die alle die aktuelle Instanziierung sind.

Basierend auf dieser Vorstellung besagt die Sprache, dass CurrentInstantiation::Foo , Foo und CurrentInstantiationTyped->Foo (wie A *a = this; a->Foo ) alle Mitglieder der aktuellen Instanziierung sind, wenn sie Mitglieder von a sind Klasse, die die aktuelle Instanziierung oder eine ihrer nicht abhängigen Basisklassen ist (indem Sie die Namenssuche sofort ausführen).

Die Schlüsselwörter typename und template sind jetzt nicht mehr erforderlich, wenn das Qualifikationsmerkmal ein Mitglied der aktuellen Instanziierung ist. Ein wichtiger Punkt, den es zu beachten gilt, ist, dass A<T> immer noch ein typabhängiger Name ist (schließlich ist T auch typabhängig). Aber A<T>::result_type ist bekanntlich ein Typ - der Compiler wird diese Art von abhängigen Typen "magisch" untersuchen, um das herauszufinden.

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

Das ist beeindruckend, aber können wir es besser machen? Die Sprache geht sogar noch weiter und erfordert, dass eine Implementierung wieder D::result_type sucht, wenn sie D::f instanziiert (selbst wenn sie ihre Bedeutung bereits zur Definitionszeit gefunden hat). Wenn nun das Nachschlageergebnis abweicht oder zu Mehrdeutigkeiten führt, ist das Programm schlecht ausgebildet und es muss eine Diagnose gegeben werden. Stellen Sie sich vor, was passiert, wenn wir C so definieren

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

Ein Compiler ist erforderlich, um den Fehler beim Instanziieren von D<int>::f zu fangen. So erhalten Sie das Beste aus den zwei Welten: "Verzögerte" Suche schützt Sie, wenn Sie Probleme mit abhängigen Basisklassen bekommen könnten, und auch "Sofort" typename , das Sie von typename und template befreit.

Unbekannte Spezialisierungen

Im Code von D ist der Name typename D::questionable_type kein Mitglied der aktuellen Instanziierung. Stattdessen markiert die Sprache es als Mitglied einer unbekannten Spezialisierung . Dies ist insbesondere immer der Fall, wenn Sie DependentTypeName::Foo oder DependentTypedName->Foo und der abhängige Typ nicht die aktuelle Instantiierung ist (in diesem Fall kann der Compiler aufgeben und sagen "Wir werden später schauen, was Foo ist ) oder es ist die aktuelle Instanziierung und der Name wurde weder in ihm noch in seinen nicht abhängigen Basisklassen gefunden und es gibt auch abhängige Basisklassen.

Stellen Sie sich vor, was passiert, wenn wir innerhalb der oben definierten A Klassenvorlage eine Memberfunktion h

void h() {
  typename A<T>::questionable_type x;
}

In C ++ 03 erlaubte die Sprache, diesen Fehler zu finden, da es niemals einen gültigen Weg geben konnte, A<T>::h zu instanziieren (welches Argument auch immer Sie T ). In C ++ 11 hat die Sprache jetzt eine weitere Überprüfung, um den Compilern mehr Grund zu geben, diese Regel zu implementieren. Da A keine abhängigen Basisklassen hat und A kein questionable_type Element deklariert, ist der Name A<T>::questionable_type weder ein Mitglied der aktuellen Instanziierung noch ein Mitglied einer unbekannten Spezialisierung. In diesem Fall sollte es nicht möglich sein, dass dieser Code zur Instanziierungszeit gültig kompiliert werden kann. Daher verbietet die Sprache einen Namen, bei dem das Qualifikationsmerkmal die aktuelle Instanz weder Mitglied einer unbekannten Spezialisierung noch Mitglied der aktuellen Instanz ist , diese Verletzung muss noch nicht diagnostiziert werden).

Beispiele und Wissenswertes

Sie können dieses Wissen in dieser Antwort versuchen und sehen, ob die obigen Definitionen für Sie in einem realen Beispiel Sinn ergeben (sie werden in dieser Antwort etwas weniger detailliert wiederholt).

Die C ++ 11-Regeln machen den folgenden gültigen C ++ 03-Code unpassend (was vom C ++ - Komitee nicht beabsichtigt war, aber wahrscheinlich nicht behoben wird)

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

Dieser gültige C ++ 03-Code würde this->f zur Instanziierungszeit an A::f binden und alles ist in Ordnung. C ++ 11 bindet es jedoch sofort an B::f und erfordert eine Doppelprüfung beim Instanziieren, um zu überprüfen, ob die Suche noch übereinstimmt. Wenn Sie jedoch C<A>::g instanziieren, gilt die Dominanzregel und die Suche findet stattdessen A::f .

Question

In welchen Templates wo und warum muss typename und template auf abhängige Namen gesetzt werden? Was genau sind abhängige Namen überhaupt? Ich habe den 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, ist in der typedef Tail::inUnion<U> dummy Zeile. Ich bin ziemlich sicher, dass inUnion ein abhängiger Name ist, und VC ++ hat recht, inUnion erstickt. Ich weiß auch, dass ich in der Lage sein sollte, eine template irgendwo hinzuzufügen, um dem Compiler mitzuteilen, dass inUnion eine Template-ID ist. Aber wo genau? Und sollte es dann annehmen, dass inUnion eine Klassenvorlage ist, dh inUnion<U> nennt einen Typ und keine Funktion?




typedef typename Tail::inUnion<U> dummy;

Ich bin mir jedoch nicht sicher, ob die Implementierung von inUnion korrekt ist. Wenn ich richtig verstehe, sollte diese Klasse nicht instanziiert werden, daher wird der Tab "fail" niemals fehlschlagen. Vielleicht wäre es besser zu zeigen, ob der Typ in der Vereinigung ist oder nicht mit einem einfachen booleschen Wert.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS: Schau dir Boost::Variant

PS2: typelists Sie einen Blick auf typelists , vor allem in Andrei Alexandrescus Buch: Modern C ++ Design




Related