Oficjalnie, po co jest nazwa maszyny?

135

Czasami widziałem kilka naprawdę nieczytelnych komunikatów o błędach wypluwanych gccpodczas korzystania z szablonów ... W szczególności miałem problemy, w których pozornie poprawne deklaracje powodowały bardzo dziwne błędy kompilacji, które magicznie znikały, dodając typenamesłowo kluczowe do początku deklaracja ... (na przykład w zeszłym tygodniu deklarowałem dwa iteratory jako elementy innej klasy z szablonami i musiałem to zrobić) ...

O co chodzi typename?

dicroce
źródło

Odpowiedzi:

211

Oto cytat z książki Josuttisa:

Słowo kluczowe typenamezostało wprowadzone w celu określenia, że ​​następujący po nim identyfikator jest typem. Rozważmy następujący przykład:

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

Tutaj typenamejest używany do wyjaśnienia, że SubTypejest to rodzaj class T. Tak więc ptrjest wskaźnikiem do typu T::SubType. Bez typename, SubType można by uznać za element statyczny. A zatem

T::SubType * ptr

byłoby pomnożeniem wartości SubTypetypu Tprzez ptr.

Naveen
źródło
2
Świetna książka. Przeczytaj go raz, a jeśli chcesz, zachowaj go jako punkt odniesienia.
deft_code
1
Wnikliwy czytelnik zda sobie sprawę, że gramatyka nie zezwala na wyrażanie mnożenia w deklaracji członków. Jako takie, C ++ 20 zwalnia z konieczności na to typename(choć nie wszystkie z nich!).
Davis Herring
Nie przekonało mnie. Po utworzeniu instancji szablonu jest bardzo dobrze zdefiniowane, czym jest podtyp T ::
kovarex
36

Post na BLogu Stana Lippmana sugeruje: -

Stroustrup ponownie użył istniejącego słowa kluczowego class, aby określić parametr typu, zamiast wprowadzać nowe słowo kluczowe, które mogłoby oczywiście uszkodzić istniejące programy. Nie chodziło o to, że nowe słowo kluczowe nie było brane pod uwagę - po prostu nie było uważane za konieczne, biorąc pod uwagę potencjalne zakłócenia. I aż do standardu ISO-c ++, to był jedyny sposób, aby zadeklarować parametr typu.

Zasadniczo Stroustrup ponownie użył słowa kluczowego class bez wprowadzania nowego słowa kluczowego, które jest później zmieniane w standardzie z następujących powodów

Jako podany przykład

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

gramatyka języka jest błędnie interpretowana T::A *aObj;jako wyrażenie arytmetyczne, dlatego wprowadzane jest nowe słowo kluczowe o nazwietypename

typename T::A* a6;

instruuje kompilator, aby traktował następną instrukcję jako deklarację.

Skoro słowo kluczowe znajdowało się na liście płac, do licha, dlaczego nie naprawić zamieszania spowodowanego pierwotną decyzją o ponownym użyciu słowa kluczowego class.

Dlatego mamy jedno i drugie

Możesz rzucić okiem na ten post , na pewno ci pomoże, właśnie wyciągnąłem z niego tyle, ile mogłem

Xinus
źródło
Tak, ale w takim razie dlaczego potrzebne było nowe słowo kluczowe typename, skoro można było użyć istniejącego słowa kluczowego classw tym samym celu?
Jesper
5
@Jesper: Myślę, że odpowiedź Xenusa jest tutaj myląca. typenamestało się konieczne, aby naprawić problem parsowania, jak opisano w odpowiedzi Naveena, cytując Josuttisa. (Myślę, że wstawienie classw tym miejscu nie zadziałałoby.) Dopiero po zaakceptowaniu nowego słowa kluczowego w tym przypadku było to również dozwolone w szablonowych deklaracjach argumentów ( czy to definicje? ), Ponieważ classzawsze istniało zwodniczy.
sbi
13

Rozważ kod

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

Niestety, kompilator nie musi być psychiczny, a nie wiem, czy T :: sometype skończy nawiązując do nazwy typu lub statycznego członek T. Tak więc, ktoś używa typename, aby poinformować go:

template<class T> somefunction( T * arg )
{
    typename T::sometype x; // works!
    .
    .
księżycowy cień
źródło
6

W niektórych sytuacjach, gdy odwołujesz się do elementu członkowskiego tak zwanego typu zależnego (co oznacza "zależny od parametru szablonu"), kompilator nie zawsze może jednoznacznie wydedukować znaczenie semantyczne powstałej konstrukcji, ponieważ nie wie, jaka to nazwa (tj. czy jest to nazwa typu, nazwa członka danych czy nazwa czegoś innego). W takich przypadkach musisz rozróżnić sytuację, jawnie mówiąc kompilatorowi, że nazwa należy do nazwy typu zdefiniowanej jako element członkowski tego zależnego typu.

Na przykład

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

W tym przykładzie słowo kluczowe jest typenameniezbędne do skompilowania kodu.

To samo dzieje się, gdy chcesz odwołać się do elementu szablonu typu zależnego, tj. Do nazwy, która określa szablon. Musisz także pomóc kompilatorowi, używając słowa kluczowego template, chociaż jest ono umieszczone inaczej

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

W niektórych przypadkach może być konieczne użycie obu

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

(jeśli poprawnie otrzymałem składnię).

Oczywiście inna rola słowa kluczowego typenamema być wykorzystywana w deklaracjach parametrów szablonu.

Mrówka
źródło
Zobacz także Opis słowa kluczowego C ++ typename, aby uzyskać więcej informacji (w tle).
Atafar
5

Sekret polega na tym, że szablon może być wyspecjalizowany dla niektórych typów. Oznacza to, że może również definiować interfejs zupełnie inny dla kilku typów. Na przykład możesz napisać:

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

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

Ktoś mógłby zapytać, dlaczego jest to przydatne i rzeczywiście: to naprawdę wygląda na bezużyteczne. Ale pamiętaj, że np std::vector<bool>. referenceTyp wygląda zupełnie inaczej niż u innych T. Wprawdzie nie zmienia rodzaju referencez typu na coś innego, ale może się zdarzyć.

Co się stanie, jeśli napiszesz własne szablony przy użyciu tego testszablonu. Coś takiego

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

wydaje się to być dla ciebie w porządku, ponieważ oczekujesz, że test<T>::ptrjest to typ. Ale kompilator tego nie wie, a nawet norma radzi mu, aby oczekiwał czegoś przeciwnego, test<T>::ptrnie jest typem. Aby powiedzieć kompilatorowi, czego się spodziewasz, musisz dodać typenameprzed. Prawidłowy szablon wygląda następująco

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

Podsumowując: musisz dodać typenameprzed każdym użyciem zagnieżdżonego typu szablonu w swoich szablonach. (Oczywiście tylko wtedy, gdy parametr szablonu twojego szablonu jest używany dla tego szablonu wewnętrznego).

flipsy
źródło
5

Dwa zastosowania:

  1. Jako templateargument słowo kluczowe (zamiast class)
  2. Słowo typenamekluczowe mówi kompilatorowi, że identyfikator jest typem (a nie statyczną zmienną składową)
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}
Phillip Ngan
źródło
4

Myślę, że wszystkie odpowiedzi wspominały, że typenamesłowo kluczowe jest używane w dwóch różnych przypadkach:

a) Podczas deklarowania parametru typu szablonu. na przykład

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

Które nie ma między nimi różnicy i są DOKŁADNIE takie same.

b) Przed użyciem zagnieżdżonej nazwy typu zależnego dla szablonu.

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

Nieużywanie typenameprowadzi do błędów analizowania / kompilacji.

To, co chcę dodać do drugiego przypadku, jak wspomniano w książce Scotta Meyersa Efektywne C ++ , to wyjątek dotyczący używania typenameprzed zagnieżdżoną nazwą typu zależnego . Wyjątkiem jest to, że jeśli używasz zagnieżdżonej nazwy typu zależnego jako klasy bazowej lub na liście inicjalizacji elementu członkowskiego , nie powinieneś używać typenametam:

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

Uwaga: użycie typenamedla drugiego przypadku (tj. Przed zagnieżdżoną nazwą typu zależnego) nie jest potrzebne od C ++ 20.

Gupta
źródło
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;
}
Praca w
źródło