Gdzie i dlaczego muszę umieszczać słowa kluczowe „szablon” i „typename”?


1125

W szablonach, gdzie i dlaczego muszę umieścić typenamei templatena 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> dummykolejce. Jestem całkiem pewien, że inUnionto nazwa zależna, a VC ++ ma rację, jeśli się ją dusi.
Wiem również, że powinienem móc dodać coś, templateaby 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ę?


1
Irytujące pytanie: dlaczego nie wzmocnić :: Variant?
Assaf Lavie

58
Wrażliwości polityczne, przenośność.
MSalters

5
Sprawiłem, że twoje rzeczywiste pytanie („Gdzie umieścić szablon / nazwa typu?”) Lepiej się wyróżnia, umieszczając ostatnie pytanie i kod na początku oraz skróciłem kod w poziomie, aby zmieścił się na ekranie 1024x.
Johannes Schaub - litb

7
Usunęliśmy „nazwy zależne” z tytułu, ponieważ wydaje się, że większość osób, które zastanawiają się nad „typename” i „template”, nie wie, czym są „nazwy zależne”. W ten sposób powinno to być dla nich mniej mylące.
Johannes Schaub - litb

2
@MSalters: boost jest dość przenośny. Powiedziałbym, że tylko polityka jest ogólnym powodem, dla którego wzmocnienie często nie jest ograniczone. Jedynym dobrym powodem, jaki znam, jest wydłużony czas budowy. W przeciwnym razie chodzi o utratę tysięcy dolarów na nowo wymyślając koło.
v.oddou

Odpowiedzi:


1161

(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 toznacza. 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::xodnosi się nazwa , jeśli todnosi się do parametru typu szablonu? xmoż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::xjest to nazwa zależna, musimy ją poprzedzić, typenameaby 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 typenamenie 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 Tparametr 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 typenametylko 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::functioni 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::functionna zero (to int()), i następnie wykorzystuje Operator większości porównać uzyskany boolprzed 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ć templatebezpoś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 Njest wyrażeniem zależnym od wartości lub Ttypem 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::xnależ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 + Nnie jest nazwą, ale Njest. 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), fjest 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 typenamei 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 templatenie 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ć templatepo 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
     };

22
Ta odpowiedź została skopiowana z mojego wcześniejszego wpisu FAQ, który usunąłem, ponieważ stwierdziłem, że powinienem lepiej wykorzystywać istniejące podobne pytania zamiast tworzyć nowe „pseudo-pytania” tylko w celu udzielenia odpowiedzi. Podziękowania dla @Prasoon , który zredagował pomysły z ostatniej części (przypadki, w których nazwa / szablon jest zabroniony) w odpowiedzi.
Johannes Schaub - litb

1
Czy możesz mi pomóc, kiedy powinienem użyć tej składni? this-> template f <int> (); Pojawia się ten błąd „szablon” (jako element ujednoznaczniający) jest dozwolony tylko w szablonach, ale bez słowa kluczowego szablon działa dobrze.
balki

1
Poprosiłem podobne pytanie dziś, że wkrótce został oznaczony jako duplikat: stackoverflow.com/questions/27923722/... . Poinstruowano mnie, aby ożywić to pytanie zamiast tworzyć nowe. Muszę powiedzieć, że nie zgadzam się na to, że są duplikatami, ale kim jestem, prawda? Czy istnieje więc jakiś typenamewymuszony powód, nawet jeśli składnia nie dopuszcza w tym momencie żadnych alternatywnych interpretacji oprócz nazw typów?
JorenHeit

1
@Pablo niczego nie brakuje. Ale nadal musiał napisać dwuznaczność, nawet jeśli cała linia nie byłaby już niejednoznaczna.
Johannes Schaub - litb

1
@Pablo ma na celu uproszczenie języka i kompilatorów. Istnieją propozycje, aby pozwolić większej liczbie sytuacji na automatyczne ustalenie, dzięki czemu słowo kluczowe jest rzadziej potrzebne. Zwróć uwagę, że w twoim przykładzie token jest niejednoznaczny i dopiero gdy zobaczysz „>” po podwójnym, możesz ujednoznacznić go jako nawias kątowy szablonu. Aby uzyskać więcej informacji, jestem niewłaściwą osobą, aby zapytać, ponieważ nie mam doświadczenia w implementacji parsera kompilatorów C ++.
Johannes Schaub - litb

135

C ++ 11

Problem

Chociaż zasady w C ++ 03 mówią o tym, kiedy potrzebujesz typenamei templatesą w dużej mierze uzasadnione, jest jedna irytująca wada jego sformułowania

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

Jak widać, potrzebujemy słowa kluczowego ujednoznaczniającego, nawet jeśli kompilator mógłby doskonale dowiedzieć się, że A::result_typemoże być tylko int(i dlatego jest typem) i this->gmoże być tylko szablonem elementu gzadeklarowanym później (nawet jeśli Ajest 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 znaczenie A!).

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 i Aoba są bieżącymi instancjami).

Na podstawie tego pojęcia, język mówi, że CurrentInstantiation::Foo, Fooi CurrentInstantiationTyped->Foo(jak A *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 typenamei nie templatesą 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źć.TA<T>::result_type

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

To imponujące, ale czy możemy to zrobić lepiej? Język idzie nawet dalej i wymaga, aby implementacja ponownie szukała D::result_typepodczas tworzenia instancji D::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 w Cten sposób

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

Kompilator 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ę od typenamei template.

Nieznane specjalizacje

W kodzie Dnazwa typename D::questionable_typenie 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 polecenie DependentTypeName::Foolub DependentTypedName->Fooalbo typem zależnym nie jest bieżąca instancja (w takim przypadku kompilator może się poddać i powiedzieć „przyjrzymy się później, co Foojest) 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 hw ramach wyżej zdefiniowanego Aszablonu klasy

void h() {
  typename A<T>::questionable_type x;
}

W 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 argument T). 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ż Anie ma na utrzymaniu klas bazowych, i Adeklaruje, żadne państwo questionable_type, nazwa A<T>::questionable_typejest 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)

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

Ten prawidłowy kod C ++ 03 byłby powiązany this->fz A::finstancją i wszystko jest w porządku. C ++ 11 jednak natychmiast go wiąże B::fi wymaga podwójnej kontroli podczas tworzenia instancji, sprawdzając, czy wyszukiwanie nadal pasuje. Jednak gdy uruchamianiu C<A>::gThe Rule Dominacja dotyczy i odnośników znajdzie A::fzamiast.


fyi - ta odpowiedź jest wymieniona tutaj: stackoverflow.com/questions/56411114/... Znaczna część kodu w tej odpowiedzi nie jest kompilowana na różnych kompilatorach.
Adam Rackis

@AdamRackis zakładając, że specyfikacja C ++ nie zmieniła się od 2013 roku (data, w której napisałem tę odpowiedź), to kompilatory, z którymi wypróbowałeś swój kod, po prostu nie implementują jeszcze tej funkcji C ++ 11 +.
Johannes Schaub - litb

98

PRZEDMOWA

Ten post ma być łatwą do odczytania alternatywą dla postu litba .

Podstawowy cel jest taki sam; wyjaśnienie „Kiedy?” i dlaczego?" typenamei templatenależy je zastosować.


Jaki jest cel typenamei template?

typenamei templatesą 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 )?

template<class T> void f_tmpl () { T::foo * x; /* <-- (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 Ts mogą drastycznie zmienić zaangażowaną semantykę.

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


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 .

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

W powyższym fragmencie mamy cztery zależne nazwy:

  • E )
    • „typ” zależy od instancji SomeTrait<T>, która obejmuje Ti;
  • F )
    • „NestedTrait” , który jest identyfikatorem szablonu , zależy od SomeTrait<T>i;
    • „typ” na końcu ( F ) zależy od NestedTrait , która zależy od SomeTrait<T>i;
  • G )
    • „dane” , które wyglądają jak szablon funkcji składowej , są pośrednio nazwą zależną, ponieważ typ foo zależy od wystąpienia 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_tmplmieć poprawną definicję, musimy wyraźnie powiedzieć kompilatorowi, że oczekujemy typu w ( E ), id-szablonu i typu w ( F ) oraz id-szablonu w ( G ).

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

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 typenamena początku naszej w pełni kwalifikowanej nazwy .

templateróż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 aplikujemy templatebezpośrednio przed każdą nazwą , którą chcielibyśmy traktować jako taką.



CZY MOGĘ WŁĄCZYĆ SŁOWA KLUCZOWE PRZED JAKĄKOLWIEK NAZWĄ?

Mogę tylko trzymać typenamei templateprzed jakimkolwiek imieniem nie chcę się martwić o kontekście, w którym się pojawiają ...? ” -Some C++ Developer

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 ).

namespace N {
  template<class T>
  struct X { };
}

         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

Uwaga : Stosowanie typenamelub templatew 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 typenamei templatewyraź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 typenamejest zarówno źle sformułowane, jak i zbędne.

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };


  • Gdy id-szablonu jest tym, do którego odwołuje się dyrektywa pochodna klasy using

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };

20
typedef typename Tail::inUnion<U> dummy;

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ą.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS: spójrz na Boost :: Variant

PS2: Spójrz na listy typograficzne , w szczególności w książce Andrei Alexandrescu: Modern C ++ Design


inUnion <U> zostałby utworzony, jeśli na przykład próbowałbyś wywołać Union <float, bool> :: operator = (U) z U == int. Wywołuje zestaw prywatny (U, inUnion <U> * = 0).
MSalters

A praca z wynikiem = prawda / fałsz polega na tym, że potrzebuję boost :: enable_if <>, co jest niezgodne z naszym obecnym zestawem narzędzi OSX. Osobny szablon jest jednak nadal dobrym pomysłem.
MSalters

Luc oznacza manekina Typedef Tail :: inUnion <U>; linia. które utworzą instancję Tail. ale nie inUnion <U>. jest tworzony natychmiast, gdy potrzebuje pełnej definicji. dzieje się tak na przykład, jeśli weźmiesz sizeof lub uzyskasz dostęp do członka (używając :: foo). @MSalters i tak masz inny problem:
Johannes Schaub - litb

-sizeof (U) nigdy nie jest ujemny :), ponieważ size_t jest liczbą całkowitą bez znaku. dostaniesz bardzo wysoką liczbę. prawdopodobnie chcesz zrobić sizeof (U)> = 1? -1: 1 lub podobny :)
Johannes Schaub - litb

zostawiłbym to niezdefiniowane i tylko to zadeklarowałem: template <typename U> struct inUnion; więc z pewnością nie można go utworzyć. myślę, że mając rozmiar sizeof, kompilator może również dać ci błąd, nawet jeśli go nie utworzysz, ponieważ jeśli wie, że sizeof (U) jest zawsze> = 1 i ...
Johannes Schaub - litb

20

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 typenamesłowa kluczowego dotyczy głównie parametru szablonu i chcesz uzyskać dostęp do zagnieżdżonego typedeflub przy użyciu aliasu, na przykład:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

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 typenamena przykład określać :

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

Ogólne zasady dodawania templatekwalifikatora 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ę:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

Próba uzyskania dostępu t.get<int>()z wnętrza funkcji spowoduje błąd:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

Dlatego w tym kontekście potrzebujesz templatesłowa kluczowego i nazwij go tak:

t.template get<int>()

W ten sposób kompilator przeanalizuje to poprawnie, a nie t.get < int.


2

Zamieszczam doskonałą odpowiedź JLBorges na podobne dosłowne pytanie z cplusplus.com, ponieważ jest to najbardziej zwięzłe wyjaśnienie na ten temat.

W szablonie, który piszemy, można użyć dwóch rodzajów nazw - nazw zależnych i nazw niezależnych. Nazwa zależna to nazwa zależna od parametru szablonu; nazwa niezależna ma to samo znaczenie niezależnie od parametrów szablonu.

Na przykład:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)

    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

To, do czego odnosi się nazwa zależna, może być czymś innym dla każdej innej instancji szablonu. W związku z tym szablony C ++ podlegają „dwufazowemu wyszukiwaniu nazw”. Kiedy szablon jest wstępnie analizowany (zanim nastąpi tworzenie instancji), kompilator wyszukuje nazwy niezależne. Kiedy ma miejsce określona instancja szablonu, parametry szablonu są wówczas znane, a kompilator wyszukuje nazwy zależne.

Podczas pierwszej fazy analizator składni musi wiedzieć, czy nazwa zależna jest nazwą typu, czy nazwą nietypu. Domyślnie zakłada się, że nazwa zależna jest nazwą typu innego niż typ. Słowo kluczowe typename przed nazwą zależną określa, że ​​jest to nazwa typu.


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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.