Wofür ist offiziell der Typname?

130

Gelegentlich habe ich einige wirklich nicht entzifferbare Fehlermeldungen gesehen, die gccbei der Verwendung von Vorlagen ausgegeben wurden ... Insbesondere hatte ich Probleme, bei denen scheinbar korrekte Deklarationen sehr seltsame Kompilierungsfehler verursachten, die auf magische Weise behoben wurden, indem das typenameSchlüsselwort dem Anfang des vorangestellt wurde Deklaration ... (Zum Beispiel habe ich erst letzte Woche zwei Iteratoren als Mitglieder einer anderen Klasse mit Vorlagen deklariert und musste dies tun) ...

Worum geht es in der Geschichte typename?

Dicroce
quelle

Antworten:

207

Es folgt das Zitat aus Josuttis Buch:

Das Schlüsselwort typenamewurde eingeführt, um anzugeben, dass der folgende Bezeichner ein Typ ist. Betrachten Sie das folgende Beispiel:

template <class T>
Class MyClass
{
  typename T::SubType * ptr;
  ...
};

Hier typenamewird verwendet, um zu verdeutlichen, dass SubTypees sich um eine Art von handelt class T. Somit ptrist ein Zeiger auf den Typ T::SubType. Ohne typename, SubType würde in Betracht gezogen wird ein statisches Element. So

T::SubType * ptr

wäre eine Multiplikation des Wertes SubTypevom Typ Tmit ptr.

Naveen
quelle
2
Tolles Buch. Lesen Sie es einmal durch und bewahren Sie es als Referenz auf, wenn Sie möchten.
Deft_code
1
Der kluge Leser wird erkennen, dass ein Multiplikationsausdruck von der Grammatik für eine Mitgliederdeklaration nicht zulässig ist. Daher verzichtet C ++ 20 darauf typename(wenn auch nicht alle!).
Davis Herring
36

Stan Lippmans BLog-Post schlägt vor:

Stroustrup verwendete das vorhandene Klassenschlüsselwort erneut , um einen Typparameter anzugeben, anstatt ein neues Schlüsselwort einzuführen, das natürlich vorhandene Programme beschädigen könnte. Es war nicht so, dass ein neues Keyword nicht berücksichtigt wurde - nur, dass es aufgrund seiner möglichen Störung nicht als notwendig angesehen wurde. Und bis in dem Standard ++ ISO-C, war dies der einzige Weg , um einen Typ - Parameter zu erklären.

Grundsätzlich hat Stroustrup das Klassenschlüsselwort wiederverwendet, ohne ein neues Schlüsselwort einzuführen, das anschließend aus den folgenden Gründen im Standard geändert wird

Als Beispiel gegeben

template <class T>
class Demonstration {
public:
void method() {
    T::A *aObj; // oops …
     // …
};

Sprachgrammatik interpretiert falsch T::A *aObj;als arithmetischer Ausdruck, so dass ein neues Schlüsselwort namens eingeführt wirdtypename

typename T::A* a6;

Es weist den Compiler an, die nachfolgende Anweisung als Deklaration zu behandeln.

Da das Schlüsselwort auf der Gehaltsliste stand, können Sie die Verwirrung beheben, die durch die ursprüngliche Entscheidung zur Wiederverwendung des Klassenschlüsselworts verursacht wurde.

Deshalb haben wir beide

Sie können sich diesen Beitrag ansehen , er wird Ihnen auf jeden Fall helfen. Ich habe nur so viel wie möglich daraus extrahiert

Xinus
quelle
Ja, aber warum war dann ein neues Schlüsselwort typenameerforderlich, wenn Sie das vorhandene Schlüsselwort classfür denselben Zweck verwenden konnten?
Jesper
5
@Jesper: Ich denke, Xenus 'Antwort ist hier verwirrend. typenamewurde notwendig, um das Parsing-Problem zu beheben, wie in Naveens Antwort beschrieben, indem Josuttis zitiert wurde. (Ich glaube nicht, dass das Einfügen eines classan dieser Stelle funktioniert hätte.) Erst nachdem das neue Schlüsselwort für diesen Fall akzeptiert wurde, war es auch in Deklarationen von Vorlagenargumenten zulässig ( oder sind das Definitionen? ), Da classes immer etwas gegeben hat irreführend.
sbi
13

Betrachten Sie den Code

template<class T> somefunction( T * arg )
{
    T::sometype x; // broken
    .
    .

Leider muss der Compiler nicht psychisch sein und weiß nicht, ob sich T :: sometype auf einen Typnamen oder ein statisches Mitglied von T bezieht. Man typenamesagt es also:

template<class T> somefunction( T * arg )
{
    typename T::sometype x; // works!
    .
    .
Mondschatten
quelle
6

In einigen Situationen, in denen Sie auf ein Mitglied des sogenannten abhängigen Typs verweisen (was "abhängig vom Vorlagenparameter" bedeutet), kann der Compiler die semantische Bedeutung des resultierenden Konstrukts nicht immer eindeutig ableiten, da er nicht weiß, um welche Art von Namen es sich handelt (dh ob es sich um einen Namen eines Typs, einen Namen eines Datenelements oder einen Namen eines anderen handelt). In solchen Fällen müssen Sie die Situation eindeutig definieren, indem Sie dem Compiler explizit mitteilen, dass der Name zu einem Typnamen gehört, der als Mitglied dieses abhängigen Typs definiert ist.

Beispielsweise

template <class T> struct S {
  typename T::type i;
};

In diesem Beispiel ist das Schlüsselwort typenameerforderlich, damit der Code kompiliert werden kann.

Dasselbe passiert, wenn Sie auf ein Vorlagenmitglied vom abhängigen Typ verweisen möchten, dh auf einen Namen, der eine Vorlage kennzeichnet. Sie müssen dem Compiler auch helfen, indem Sie das Schlüsselwort verwenden template, obwohl es anders platziert ist

template <class T> struct S {
  T::template ptr<int> p;
};

In einigen Fällen kann es erforderlich sein, beide zu verwenden

template <class T> struct S {
  typename T::template ptr<int>::type i;
};

(wenn ich die Syntax richtig verstanden habe).

Natürlich soll eine andere Rolle des Schlüsselworts typenamein Vorlagenparameterdeklarationen verwendet werden.

Ameise
quelle
Weitere (Hintergrund-) Informationen finden Sie unter Eine Beschreibung des Schlüsselworts C ++ Typname .
Atafar
5

Das Geheimnis liegt in der Tatsache, dass eine Vorlage für einige Typen spezialisiert werden kann. Dies bedeutet, dass die Schnittstelle für verschiedene Typen völlig unterschiedlich definiert werden kann. Zum Beispiel können Sie schreiben:

template<typename T>
struct test {
    typedef T* ptr;
};

template<>         // complete specialization 
struct test<int> { // for the case T is int
    T* ptr;
};

Man könnte fragen, warum dies nützlich ist und in der Tat: Das sieht wirklich nutzlos aus. Beachten Sie jedoch, dass std::vector<bool>der referenceTyp beispielsweise völlig anders aussieht als bei anderen Ts. Zugegeben, es ändert nicht die Art von referenceTyp zu etwas anderem, aber es könnte trotzdem passieren.

Was passiert nun, wenn Sie mit dieser testVorlage eigene Vorlagen schreiben? Etwas wie das

template<typename T>
void print(T& x) {
    test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

Es scheint für Sie in Ordnung zu sein, weil Sie erwarten, dass dies test<T>::ptrein Typ ist. Aber der Compiler weiß es nicht und in der Tat wird ihm vom Standard sogar geraten, das Gegenteil zu erwarten, es test<T>::ptrist kein Typ. Um dem Compiler mitzuteilen, was Sie erwarten, müssen Sie zuvor ein hinzufügen typename. Die richtige Vorlage sieht so aus

template<typename T>
void print(T& x) {
    typename test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

Fazit: Sie müssen typenamevorher hinzufügen, wann immer Sie einen verschachtelten Typ einer Vorlage in Ihren Vorlagen verwenden. (Natürlich nur, wenn ein Vorlagenparameter Ihrer Vorlage für diese innere Vorlage verwendet wird.)

Phlipsy
quelle
5

Zwei Anwendungen:

  1. Als templateArgument Schlüsselwort (anstelle von class)
  2. Ein typenameSchlüsselwort teilt dem Compiler mit, dass ein Bezeichner ein Typ ist (und keine statische Elementvariable).
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}
Phillip Ngan
quelle
4

Ich denke, alle Antworten haben erwähnt, dass das typenameSchlüsselwort in zwei verschiedenen Fällen verwendet wird:

a) Beim Deklarieren eines Vorlagentypparameters. z.B

template<class T> class MyClass{};        // these two cases are
template<typename T> class MyNewClass{};  // exactly the same.

Was es keinen Unterschied zwischen ihnen gibt und sie genau gleich sind.

b) Bevor Sie einen verschachtelten abhängigen Typnamen für eine Vorlage verwenden.

template<class T>
void foo(const T & param)
{
   typename T::NestedType * value; // we should use typename here
}

Was nicht verwendet wird, typenameführt zu Analyse- / Kompilierungsfehlern.

Was ich dem zweiten Fall hinzufügen möchte, wie in Scot Meyers Buch Effective C ++ erwähnt , ist, dass es eine Ausnahme gibt, typenamevor einem verschachtelten abhängigen Typnamen zu verwenden . Die Ausnahme besteht darin, dass Sie den verschachtelten abhängigen Typnamen entweder als Basisklasse oder in einer Elementinitialisierungsliste nicht verwenden typenamesollten:

template<class T>
class D : public B<T>::NestedType               // No need for typename here
{
public:
   D(std::string str) : B<T>::NestedType(str)   // No need for typename here
   {
      typename B<T>::AnotherNestedType * x;     // typename is needed here
   }
}

Hinweis: Die Verwendung typenamefür den zweiten Fall (dh vor dem Namen des verschachtelten abhängigen Typs) ist seit C ++ 20 nicht mehr erforderlich.

Gupta
quelle
2
#include <iostream>

class A {
public:
    typedef int my_t;
};

template <class T>
class B {
public:
    // T::my_t *ptr; // It will produce compilation error
    typename T::my_t *ptr; // It will output 5
};

int main() {
    B<A> b;
    int my_int = 5;
    b.ptr = &my_int;
    std::cout << *b.ptr;
    std::cin.ignore();
    return 0;
}
Job in
quelle