Czasami widziałem kilka naprawdę nieczytelnych komunikatów o błędach wypluwanych gcc
podczas 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 typename
sł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
?
Odpowiedzi:
Oto cytat z książki Josuttisa:
źródło
typename
(choć nie wszystkie z nich!).Post na BLogu Stana Lippmana sugeruje: -
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ę.
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
źródło
typename
, skoro można było użyć istniejącego słowa kluczowegoclass
w tym samym celu?typename
stało się konieczne, aby naprawić problem parsowania, jak opisano w odpowiedzi Naveena, cytując Josuttisa. (Myślę, że wstawienieclass
w 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żclass
zawsze istniało zwodniczy.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! . .
źródło
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
typename
niezbę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 inaczejtemplate <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
typename
ma być wykorzystywana w deklaracjach parametrów szablonu.źródło
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>
.reference
Typ wygląda zupełnie inaczej niż u innychT
. Wprawdzie nie zmienia rodzajureference
z typu na coś innego, ale może się zdarzyć.Co się stanie, jeśli napiszesz własne szablony przy użyciu tego
test
szablonu. Coś takiegotemplate<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>::ptr
jest to typ. Ale kompilator tego nie wie, a nawet norma radzi mu, aby oczekiwał czegoś przeciwnego,test<T>::ptr
nie jest typem. Aby powiedzieć kompilatorowi, czego się spodziewasz, musisz dodaćtypename
przed. Prawidłowy szablon wygląda następującotemplate<typename T> void print(T& x) { typename test<T>::ptr p = &x; std::cout << *p << std::endl; }
Podsumowując: musisz dodać
typename
przed 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).źródło
Dwa zastosowania:
template
argument słowo kluczowe (zamiastclass
)typename
kluczowe mówi kompilatorowi, że identyfikator jest typem (a nie statyczną zmienną składową)źródło
Myślę, że wszystkie odpowiedzi wspominały, że
typename
sł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
typename
prowadzi 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
typename
przed 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ćtypename
tam: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
typename
dla drugiego przypadku (tj. Przed zagnieżdżoną nazwą typu zależnego) nie jest potrzebne od C ++ 20.źródło
#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; }
źródło