(Zobacz tutaj również moją odpowiedź w C ++ 11 )
Aby przeanalizować program C ++, kompilator musi wiedzieć, czy niektóre nazwy są typami, czy nie. Poniższy przykład pokazuje, że:
t * f;
Jak należy to przeanalizować? W wielu językach kompilator nie musi znać znaczenia nazwy, aby analizować i w zasadzie wiedzieć, jakie działanie wykonuje wiersz kodu. W C ++ powyższe może jednak dać bardzo różne interpretacje w zależności od tego, co t
oznacza. Jeśli jest to typ, będzie to deklaracja wskaźnika f
. Jeśli jednak nie jest typem, będzie to mnożenie. Tak więc standard C ++ mówi w akapicie (3/7):
Niektóre nazwy oznaczają typy lub szablony. Zasadniczo za każdym razem, gdy napotyka się nazwę, konieczne jest ustalenie, czy nazwa ta oznacza jedną z tych jednostek, przed dalszym analizowaniem programu, który ją zawiera. Proces, który to określa, nazywa się wyszukiwaniem nazw.
Jak kompilator dowie się, do czego t::x
odnosi się nazwa , jeśli t
odnosi się do parametru typu szablonu? x
może być statycznym elementem danych int, który może zostać pomnożony lub równie dobrze może być klasą zagnieżdżoną lub typedef, która mogłaby ulec deklaracji. Jeśli nazwa ma tę właściwość - której nie można wyszukać, dopóki nie zostaną poznane rzeczywiste argumenty szablonu - wówczas nazywa się ją zależną nazwą („zależy” od parametrów szablonu).
Możesz po prostu poczekać, aż użytkownik utworzy instancję szablonu:
Poczekaj, aż użytkownik utworzy instancję szablonu, a następnie dowiedz się, jakie jest prawdziwe znaczenie t::x * f;
.
To zadziała i faktycznie jest dozwolone przez Standard jako możliwe podejście do wdrożenia. Kompilatory te w zasadzie kopiują tekst szablonu do bufora wewnętrznego i tylko wtedy, gdy potrzebna jest instancja, analizują szablon i ewentualnie wykrywają błędy w definicji. Ale zamiast niepokoić użytkowników szablonu (biedni koledzy!) Błędami popełnionymi przez autora szablonu, inne implementacje wybierają sprawdzanie szablonów wcześnie i zgłaszanie błędów w definicji tak szybko, jak to możliwe, zanim nastąpi tworzenie instancji.
Musi więc istnieć sposób poinformowania kompilatora, że niektóre nazwy są typami, a niektóre nie są.
Słowo kluczowe „typename”
Odpowiedź brzmi: Mamy zdecydować, w jaki sposób kompilator powinien przeanalizować to. Jeśli t::x
jest to nazwa zależna, musimy ją poprzedzić, typename
aby poinformować kompilator, aby przeanalizował ją w określony sposób. Standard mówi w (14.6 / 2):
Zakłada się, że nazwa używana w deklaracji lub definicji szablonu, która jest zależna od parametru szablonu, nie nadaje nazwy typowi, chyba że odpowiednie wyszukiwanie nazwy znajdzie nazwę typu lub nazwa kwalifikowana jest przez słowo kluczowe typename.
Istnieje wiele nazw, dla których typename
nie jest to konieczne, ponieważ kompilator może, przy pomocy odpowiedniego wyszukiwania nazw w definicji szablonu, dowiedzieć się, jak parsować samą konstrukcję - na przykład za pomocą T *f;
, kiedy T
parametr typu szablonu. Ale t::x * f;
aby była to deklaracja, musi być napisana jako typename t::x *f;
. Jeśli słowo kluczowe zostanie pominięte, a nazwa zostanie uznana za nietypową, ale gdy wystąpi wystąpienie oznaczające typ, oznacza to, że kompilator emituje zwykłe komunikaty o błędach. Czasami błąd jest podawany w momencie definicji:
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
Składnia dopuszcza typename
tylko przed nazwami kwalifikowanymi - przyjmuje się zatem, że nazwy niekwalifikowane zawsze odnoszą się do typów, jeśli to robią.
Podobna gotcha istnieje dla nazw oznaczających szablony, jak wskazano w tekście wprowadzającym.
Słowo kluczowe „szablon”
Pamiętasz wstępny cytat powyżej i jak Standard wymaga specjalnej obsługi szablonów? Weźmy następujący niewinny wygląd:
boost::function< int() > f;
Dla ludzkiego czytelnika może to wydawać się oczywiste. Nie dotyczy to kompilatora. Wyobraź sobie następującą arbitralną definicję boost::function
i f
:
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
To właściwie prawidłowe wyrażenie ! Wykorzystuje on mniej niż operatora porównaj boost::function
na zero (to int()
), i następnie wykorzystuje Operator większości porównać uzyskany bool
przed f
. Jednak, jak zapewne wiesz, boost::function
w rzeczywistości jest szablonem, więc kompilator wie (14.2 / 3):
Po wyszukaniu nazwy (3.4) okazuje się, że nazwa jest nazwą szablonu, jeśli po nazwie następuje <, to <jest zawsze traktowane jako początek listy argumentów szablonu, a nigdy jako nazwa, po której następuje „ niż operator.
Teraz wróciliśmy do tego samego problemu, co z typename
. Co jeśli nie wiemy jeszcze, czy nazwa jest szablonem podczas analizowania kodu? Będziemy musieli wstawić template
bezpośrednio przed nazwą szablonu, jak określono przez 14.2/4
. To wygląda jak:
t::template f<int>(); // call a function template
Nazwy szablonów mogą występować nie tylko po, ::
ale również po dostępie członka klasy ->
lub .
. Musisz tam również wstawić słowo kluczowe:
this->template f<int>(); // call a function template
Zależności
Dla ludzi, którzy mają na półkach grube książki Standardese i chcą wiedzieć, o czym dokładnie mówiłem, powiem trochę o tym, jak to jest określone w standardzie.
W deklaracjach szablonów niektóre konstrukcje mają różne znaczenie w zależności od tego, jakich argumentów szablonu używasz do tworzenia szablonu: Wyrażenia mogą mieć różne typy lub wartości, zmienne mogą mieć różne typy, a wywołania funkcji mogą wywoływać różne funkcje. Mówi się, że takie konstrukcje zależą od parametrów szablonu.
Standard precyzyjnie określa reguły, niezależnie od tego, czy konstrukcja jest zależna, czy nie. Dzieli je na logicznie różne grupy: jeden łapie typy, drugi łapie wyrażenia. Wyrażenia mogą zależeć od ich wartości i / lub rodzaju. Mamy więc, z dołączonymi typowymi przykładami:
- Typy zależne (np .: parametr szablonu typu
T
)
- Wyrażenia zależne od wartości (np .: parametr szablonu inny niż typ
N
)
- Wyrażenia zależne od typu (np .: rzut na parametr szablonu typu
(T)0
)
Większość reguł jest intuicyjna i rekurencyjnie budowana: Na przykład typ skonstruowany jako T[N]
typ zależny, jeśli N
jest wyrażeniem zależnym od wartości lub T
typem zależnym. Szczegóły tego można przeczytać w sekcji (14.6.2/1
) dla typów zależnych, (14.6.2.2)
dla wyrażeń zależnych od typu i (14.6.2.3)
dla wyrażeń zależnych od wartości.
Nazwy zależne
Standard jest nieco niejasny, co to dokładnie jest nazwa zależna . W prostym czytaniu (wiesz, zasada najmniejszego zaskoczenia) wszystko, co definiuje jako nazwę zależną, jest szczególnym przypadkiem dla nazw funkcji poniżej. Ponieważ jednak wyraźnie T::x
należy go również odszukać w kontekście tworzenia instancji, musi on również być zależną nazwą (na szczęście od połowy C ++ 14 komitet zaczął badać, jak naprawić tę mylącą definicję).
Aby uniknąć tego problemu, skorzystałem z prostej interpretacji tekstu standardowego. Ze wszystkich konstrukcji, które oznaczają typy zależne lub wyrażenia, ich podzbiór reprezentuje nazwy. Nazwy te są zatem „nazwami zależnymi”. Nazwa może przybierać różne formy - standard mówi:
Nazwa to użycie identyfikatora (2.11), identyfikatora funkcji operatora (13.5), identyfikatora funkcji konwersji (12.3.2) lub identyfikatora szablonu (14.2), który oznacza byt lub etykietę (6.6.4, 6.1)
Identyfikator to zwykła sekwencja znaków / cyfr, a kolejne dwa to operator +
i operator type
. Ostatnia forma to template-name <argument list>
. Wszystkie są nazwami, a przy konwencjonalnym użyciu w standardzie nazwa może również zawierać kwalifikatory, które mówią, w jakiej przestrzeni nazw lub klasie należy wyszukać nazwę.
Wyrażenie zależne od wartości 1 + N
nie jest nazwą, ale N
jest. Podzbiór wszystkich zależnych konstrukcji, które są nazwami, nazywa się nazwą zależną . Nazwy funkcji mogą jednak mieć różne znaczenie w różnych instancjach szablonu, ale niestety nie są objęte tą ogólną regułą.
Nazwy funkcji zależnych
Nie chodzi przede wszystkim o ten artykuł, ale nadal warto wspomnieć: Nazwy funkcji są wyjątkami, które są obsługiwane osobno. Nazwa funkcji identyfikatora nie zależy sama od siebie, ale od wyrażeń argumentów zależnych od typu używanych w wywołaniu. Na przykład f((T)0)
, f
jest to nazwa zależne. W standardzie jest to określone w (14.6.2/1)
.
Dodatkowe uwagi i przykłady
W wystarczającej liczbie przypadków potrzebujemy zarówno typename
i template
. Twój kod powinien wyglądać następująco
template <typename T, typename Tail>
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
typedef typename Tail::template inUnion<U> dummy;
};
// ...
};
Słowo kluczowe template
nie zawsze musi pojawiać się w ostatniej części nazwy. Może pojawić się na środku przed nazwą klasy, która jest używana jako zakres, jak w poniższym przykładzie
typename t::template iterator<int>::value_type v;
W niektórych przypadkach słowa kluczowe są zabronione, jak opisano poniżej
Na nazwę zależnej klasy podstawowej nie wolno pisać typename
. Zakłada się, że podana nazwa jest nazwą typu klasy. Dotyczy to obu nazw na liście klas podstawowych i na liście inicjalizacyjnej konstruktora:
template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };
W deklaracjach użycia nie można używać template
po ostatnim ::
, a komitet C ++ powiedział, że nie pracuje nad rozwiązaniem.
template <typename T>
struct derive_from_Has_type : SomeBase<T> {
using SomeBase<T>::template type; // error
using typename SomeBase<T>::type; // typename *is* allowed
};