W szablonach, gdzie i dlaczego muszę umieścić typename
i template
na nazwach zależnych?
Czym właściwie są nazwy zależne?
Mam następujący kod:
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> {
};
};
Problem, który mam, jest w typedef Tail::inUnion<U> dummy
kolejce. Jestem całkiem pewien, że inUnion
to nazwa zależna, a VC ++ ma rację, jeśli się ją dusi.
Wiem również, że powinienem móc dodać coś, template
aby poinformować kompilator, że inUnion jest identyfikatorem szablonu. Ale gdzie dokładnie? I czy powinien zatem założyć, że inUnion jest szablonem klasy, tzn. Nazywa inUnion<U>
typ, a nie funkcję?
Odpowiedzi:
(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:
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źnikaf
. Jeśli jednak nie jest typem, będzie to mnożenie. Tak więc standard C ++ mówi w akapicie (3/7):Jak kompilator dowie się, do czego
t::x
odnosi się nazwa , jeślit
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:
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):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;
, kiedyT
parametr typu szablonu. Alet::x * f;
aby była to deklaracja, musi być napisana jakotypename 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: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:
Dla ludzkiego czytelnika może to wydawać się oczywiste. Nie dotyczy to kompilatora. Wyobraź sobie następującą arbitralną definicję
boost::function
if
:To właściwie prawidłowe wyrażenie ! Wykorzystuje on mniej niż operatora porównaj
boost::function
na zero (toint()
), i następnie wykorzystuje Operator większości porównać uzyskanybool
przedf
. Jednak, jak zapewne wiesz,boost::function
w rzeczywistości jest szablonem, więc kompilator wie (14.2 / 3):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 przez14.2/4
. To wygląda jak: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: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:
T
)N
)(T)0
)Większość reguł jest intuicyjna i rekurencyjnie budowana: Na przykład typ skonstruowany jako
T[N]
typ zależny, jeśliN
jest wyrażeniem zależnym od wartości lubT
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:
Identyfikator to zwykła sekwencja znaków / cyfr, a kolejne dwa to
operator +
ioperator type
. Ostatnia forma totemplate-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ą, aleN
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
itemplate
. Twój kod powinien wyglądać następującoSł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ładzieW 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:W deklaracjach użycia nie można używać
template
po ostatnim::
, a komitet C ++ powiedział, że nie pracuje nad rozwiązaniem.źródło
typename
wymuszony powód, nawet jeśli składnia nie dopuszcza w tym momencie żadnych alternatywnych interpretacji oprócz nazw typów?C ++ 11
Problem
Chociaż zasady w C ++ 03 mówią o tym, kiedy potrzebujesz
typename
itemplate
są w dużej mierze uzasadnione, jest jedna irytująca wada jego sformułowaniaJak widać, potrzebujemy słowa kluczowego ujednoznaczniającego, nawet jeśli kompilator mógłby doskonale dowiedzieć się, że
A::result_type
może być tylkoint
(i dlatego jest typem) ithis->g
może być tylko szablonem elementug
zadeklarowanym później (nawet jeśliA
jest gdzieś wyraźnie wyspecjalizowany, to nie wpływa na kod w tym szablonie, więc jego późniejsza specjalizacja nie może wpływać na jego znaczenieA
!).Bieżąca instancja
Aby poprawić sytuację, w C ++ 11 język śledzi, gdy typ odwołuje się do otaczającego szablonu. Aby dowiedzieć się, że typ musi zostały utworzone za pomocą pewną formę nazwy, która jest własnym imieniu (w powyższym
A
,A<T>
,::A<T>
). Typ, do którego odnosi się taka nazwa, jest znany jako bieżąca instancja . Może istnieć wiele typów, które są całą bieżącą instancją, jeśli typ, z którego tworzona jest nazwa, jest klasą członkowską / zagnieżdżoną (wówczasA::NestedClass
iA
oba są bieżącymi instancjami).Na podstawie tego pojęcia, język mówi, że
CurrentInstantiation::Foo
,Foo
iCurrentInstantiationTyped->Foo
(jakA *a = this; a->Foo
) są członkiem bieżącej instancji , jeżeli znajdują się one być członkami klasy, która jest obecna instancji lub jedna z jego nie-zależnych klas bazowych (po prostu robi wyszukiwanie nazwy natychmiast).Słowa kluczowe
typename
i nietemplate
są już wymagane, jeśli kwalifikator należy do bieżącej instancji. Kluczowym punktem tutaj do zapamiętania jest to, że wciążA<T>
jest to nazwa zależna od typu (w końcu jest również zależna od typu). Ale wiadomo, że jest typem - kompilator „magicznie” zajrzy do tego rodzaju zależnych typów, aby to rozgryźć.T
A<T>::result_type
To imponujące, ale czy możemy to zrobić lepiej? Język idzie nawet dalej i wymaga, aby implementacja ponownie szukała
D::result_type
podczas tworzenia instancjiD::f
(nawet jeśli znalazła swoje znaczenie już w momencie definiowania). Kiedy teraz wynik wyszukiwania różni się lub powoduje niejednoznaczność, program jest źle sformułowany i należy podać diagnostykę. Wyobraź sobie, co się stanie, jeśli zdefiniujemy wC
ten sposóbKompilator jest wymagany do wychwycenia błędu podczas tworzenia instancji
D<int>::f
. Otrzymujesz więc najlepszy z dwóch światów: wyszukiwanie „Opóźnione” chroniące cię, jeśli możesz mieć kłopoty z zależnymi klasami podstawowymi, a także wyszukiwanie „Natychmiastowe”, które uwalnia cię odtypename
itemplate
.Nieznane specjalizacje
W kodzie
D
nazwatypename D::questionable_type
nie jest członkiem bieżącej instancji. Zamiast tego język oznacza go jako członka nieznanej specjalizacji . W szczególności dzieje się tak zawsze, gdy wykonujesz polecenieDependentTypeName::Foo
lubDependentTypedName->Foo
albo typem zależnym nie jest bieżąca instancja (w takim przypadku kompilator może się poddać i powiedzieć „przyjrzymy się później, coFoo
jest) lub jest to bieżąca instancja i nie znaleziono w nim nazwy lub jej niezależnych klas bazowych, a także istnieją zależne klasy bazowe.Wyobraź sobie, co się stanie, jeśli będziemy mieli funkcję członka
h
w ramach wyżej zdefiniowanegoA
szablonu klasyW C ++ 03 język pozwalał na wychwycenie tego błędu, ponieważ nigdy nie istniał prawidłowy sposób tworzenia instancji
A<T>::h
(bez względu na podany argumentT
). W C ++ 11 język ma teraz dodatkową kontrolę, aby dać więcej powodów dla kompilatorów do wdrożenia tej reguły. PonieważA
nie ma na utrzymaniu klas bazowych, iA
deklaruje, żadne państwoquestionable_type
, nazwaA<T>::questionable_type
jest ani członkiem bieżącej instancji aniczłonek nieznanej specjalizacji. W takim przypadku nie powinno być mowy o tym, aby ten kod mógł poprawnie kompilować się w czasie tworzenia instancji, więc język zabrania nazwy, w której kwalifikator jest bieżącą instancją, ani nie jest członkiem nieznanej specjalizacji, ani członkiem bieżącej instancji (jednak , to naruszenie nadal nie musi być zdiagnozowane).Przykłady i ciekawostki
Możesz wypróbować tę wiedzę na temat tej odpowiedzi i sprawdzić, czy powyższe definicje mają dla Ciebie sens na przykładzie z prawdziwego świata (zostały one powtórzone nieco mniej szczegółowo w tej odpowiedzi).
Reguły C ++ 11 powodują, że następujący prawidłowy kod C ++ 03 jest źle sformułowany (co nie było zamierzone przez komitet C ++, ale prawdopodobnie nie zostanie naprawione)
Ten prawidłowy kod C ++ 03 byłby powiązany
this->f
zA::f
instancją i wszystko jest w porządku. C ++ 11 jednak natychmiast go wiążeB::f
i wymaga podwójnej kontroli podczas tworzenia instancji, sprawdzając, czy wyszukiwanie nadal pasuje. Jednak gdy uruchamianiuC<A>::g
The Rule Dominacja dotyczy i odnośników znajdzieA::f
zamiast.źródło
Jaki jest cel
typename
itemplate
?typename
itemplate
są użyteczne w okolicznościach innych niż podczas deklarowania szablonu.Istnieją pewne konteksty w C ++, w których kompilator musi zostać wyraźnie poinformowany, jak traktować nazwę, a wszystkie te konteksty mają jedną wspólną cechę; zależą od co najmniej jednego parametru szablonu .
Odwołujemy się do takich nazw, w których może występować dwuznaczność w interpretacji, jak; „ nazwy zależne ”.
Ten post wyjaśni wyjaśnienie związku między nazwami zależnymi a dwoma słowami kluczowymi.
SNIPPET MÓWI PONAD 1000 SŁÓW
Spróbuj wyjaśnić, co się dzieje w poniższym szablonie funkcji , albo sobie, przyjacielowi, a może twojemu kotowi; co się dzieje w stwierdzeniu oznaczonym ( A )?
Może to nie być tak łatwe, jak się wydaje, a dokładniej wynik oceny ( A ) w dużej mierze zależy od definicji typu przekazywanego jako parametr-szablon
T
.Różne
T
s mogą drastycznie zmienić zaangażowaną semantykę.Dwa różne scenariusze :
Jeśli utworzymy instancję szablonu funkcji z typem X , jak w ( C ), będziemy mieli deklarację wskaźnika do int o nazwie x , ale;
jeśli utworzymy instancję szablonu typu Y , jak w ( D ), ( A ) składałoby się z wyrażenia, które oblicza iloczyn 123 pomnożony przez pewną już zadeklarowaną zmienną x .
UZASADNIENIE
Standard C ++ dba o nasze bezpieczeństwo i dobre samopoczucie, przynajmniej w tym przypadku.
Aby zapobiec potencjalnemu narażeniu implementacji na paskudne niespodzianki, Standard nakazuje, abyśmy rozwikłali dwuznaczność nazwy zależnej , wyraźnie określając zamiar w dowolnym miejscu, w którym chcielibyśmy traktować nazwę jako nazwę typu lub szablon id .
Jeśli nic nie zostanie określone, nazwa zależna będzie uważana za zmienną lub funkcję.
W JAKI SPOSÓB OBSŁUGIWAĆ NAZWY ZALEŻNE ?
Gdyby to był film z Hollywood, nazwy zależne byłyby chorobą, która rozprzestrzenia się poprzez kontakt z ciałem, natychmiast wpływa na gospodarza, powodując zamieszanie. Zamieszanie, które może doprowadzić do źle sformułowanego programu perso-, erhm ..
Nazwa zależna to dowolna nazwa, która bezpośrednio lub pośrednio zależy od parametru szablonu .
W powyższym fragmencie mamy cztery zależne nazwy:
SomeTrait<T>
, która obejmujeT
i;SomeTrait<T>
i;SomeTrait<T>
i;SomeTrait<T>
.Żadna z instrukcji ( E ), ( F ) lub ( G ) nie jest poprawna, jeśli kompilator interpretuje nazwy zależne jako zmienne / funkcje (co, jak stwierdzono wcześniej, dzieje się, jeśli nie mówimy wprost inaczej).
ROZWIĄZANIE
Aby
g_tmpl
mieć poprawną definicję, musimy wyraźnie powiedzieć kompilatorowi, że oczekujemy typu w ( E ), id-szablonu i typu w ( F ) oraz id-szablonu w ( G ).Za każdym razem, gdy nazwa oznacza typ, wszystkie nazwy muszą być albo nazwami typu, albo przestrzeniami nazw , dlatego też łatwo zauważyć, że stosujemy je
typename
na początku naszej w pełni kwalifikowanej nazwy .template
różni się jednak pod tym względem, ponieważ nie można dojść do takiego wniosku; „och, to jest szablon, wtedy ta druga rzecz musi być również szablonem” . Oznacza to, że aplikujemytemplate
bezpośrednio przed każdą nazwą , którą chcielibyśmy traktować jako taką.CZY MOGĘ WŁĄCZYĆ SŁOWA KLUCZOWE PRZED JAKĄKOLWIEK NAZWĄ?
Reguły w standardzie stanowią, że możesz stosować słowa kluczowe, o ile masz do czynienia z kwalifikowaną nazwą ( K ), ale jeśli nazwa nie jest kwalifikowana, aplikacja jest źle sformułowana ( L ).
Uwaga : Stosowanie
typename
lubtemplate
w kontekście, w którym nie jest to wymagane, nie jest uważane za dobrą praktykę; tylko dlatego, że możesz coś zrobić, nie znaczy, że powinieneś.Dodatkowo istnieje konteksty gdzie
typename
itemplate
są wyraźnie niedozwolone:Określając podstawy, które dziedziczy klasa
Każda nazwa zapisana na liście bazowej specyfikatora klasy pochodnej jest już traktowana jako nazwa typu , wyraźne określenie
typename
jest zarówno źle sformułowane, jak i zbędne.Gdy id-szablonu jest tym, do którego odwołuje się dyrektywa pochodna klasy using
źródło
Nie jestem jednak pewien, czy implementacja inUnion jest poprawna. Jeśli dobrze rozumiem, nie należy tworzyć instancji tej klasy, dlatego karta „fail” nigdy nie zawiedzie. Być może lepiej byłoby wskazać, czy typ jest w unii, czy nie, z prostą wartością logiczną.
PS: spójrz na Boost :: Variant
PS2: Spójrz na listy typograficzne , w szczególności w książce Andrei Alexandrescu: Modern C ++ Design
źródło
Ta odpowiedź ma być raczej krótka i słodka, aby odpowiedzieć (częściowo) na tytułowe pytanie. Jeśli potrzebujesz odpowiedzi z bardziej szczegółowymi wyjaśnieniami, dlaczego musisz je tam umieścić, przejdź tutaj .
Ogólna zasada umieszczania
typename
słowa kluczowego dotyczy głównie parametru szablonu i chcesz uzyskać dostęp do zagnieżdżonegotypedef
lub przy użyciu aliasu, na przykład:Pamiętaj, że dotyczy to również meta funkcji lub rzeczy, które przyjmują ogólne parametry szablonu. Jeśli jednak parametr szablonu jest jawny, nie musisz
typename
na przykład określać :Ogólne zasady dodawania
template
kwalifikatora są w większości podobne, z tym że zazwyczaj obejmują funkcje składające się z szablonów (statyczne lub inne) struktury / klasy, która sama jest szablonowana, na przykład:Biorąc pod uwagę tę strukturę i funkcję:
Próba uzyskania dostępu
t.get<int>()
z wnętrza funkcji spowoduje błąd:Dlatego w tym kontekście potrzebujesz
template
słowa kluczowego i nazwij go tak:t.template get<int>()
W ten sposób kompilator przeanalizuje to poprawnie, a nie
t.get < int
.źródło
Zamieszczam doskonałą odpowiedź JLBorges na podobne dosłowne pytanie z cplusplus.com, ponieważ jest to najbardziej zwięzłe wyjaśnienie na ten temat.
Podsumowanie
Używaj słowa kluczowego typename tylko w deklaracjach i definicjach szablonów, pod warunkiem, że masz kwalifikowaną nazwę, która odnosi się do typu i zależy od parametru szablonu.
źródło