Dove e perché devo inserire le parole chiave “template” e “typename”?

Nei template, dove e perché devo inserire typename e template sui nomi dipendenti? Quali sono esattamente i nomi dipendenti comunque? Ho il codice seguente:

 template  // Tail will be a UnionNode too. struct UnionNode : public Tail { // ... template struct inUnion { // Q: where to add typename/template here? typedef Tail::inUnion dummy; }; template struct inUnion { }; }; template  // For the last node Tn. struct UnionNode { // ... template struct inUnion { char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U }; template struct inUnion { }; }; 

Il problema che ho è nella riga typedef Tail::inUnion dummy . Sono abbastanza certo che inUnion è un nome dipendente, e VC ++ ha perfettamente ragione a soffocarlo. So anche che dovrei essere in grado di aggiungere template da qualche parte per dire al compilatore che inUnion è un id-template. Ma dove esattamente? E dovrebbe quindi supporre che inUnion sia un modello di class, cioè inUnion nomi un tipo e non una funzione?

    Per analizzare un programma C ++, il compilatore deve sapere se certi nomi sono o meno tipi. Il seguente esempio dimostra che:

     t * f; 

    Come dovrebbe essere analizzato? Per molte lingue un compilatore non ha bisogno di conoscere il significato di un nome per poter analizzare e in pratica sapere quale azione fa una linea di codice. In C ++, tuttavia, quanto sopra può dare interpretazioni molto diverse a seconda di cosa significhi. Se è un tipo, allora sarà una dichiarazione di un puntatore f . Tuttavia, se non è un tipo, sarà una moltiplicazione. Quindi lo standard C ++ dice al paragrafo (3/7):

    Alcuni nomi indicano tipi o modelli. In generale, ogni volta che si incontra un nome è necessario determinare se quel nome denoti una di queste quadro prima di continuare ad analizzare il programma che lo contiene. Il processo che determina ciò è chiamato ricerca del nome.

    Come farà il compilatore a scoprire a cosa si riferisce un nome t::x , se t riferisce ad un parametro di tipo template? x potrebbe essere un membro di dati int statico che potrebbe essere moltiplicato o potrebbe ugualmente essere una class nidificata o typedef che potrebbe produrre una dichiarazione. Se un nome ha questa proprietà – che non può essere cercato fino a quando non si conoscono gli argomenti del template effettivo – allora viene chiamato un nome dipendente (“dipende” dai parametri del template).

    Si consiglia di attendere fino a quando l’utente non istanzia il modello:

    Aspettiamo fino a quando l’utente non istanzia il modello, e successivamente scopriremo il vero significato di t::x * f; .

    Questo funzionerà e in realtà è consentito dallo Standard come ansible approccio di implementazione. Questi compilatori sostanzialmente copiano il testo del modello in un buffer interno e solo quando è necessaria un’istanza, analizzano il modello e possono eventualmente rilevare errori nella definizione. Ma invece di infastidire gli utenti del modello (colleghi poveri!) Con errori dell’autore di un modello, altre implementazioni scelgono di controllare i template in anticipo e danno errori nella definizione il prima ansible, prima ancora che un’istanza abbia luogo.

    Quindi deve esserci un modo per dire al compilatore che certi nomi sono tipi e che certi nomi non lo sono.

    La parola chiave “typename”

    La risposta è: decidiamo come il compilatore dovrebbe analizzare questo. Se t::x è un nome dipendente, allora abbiamo bisogno di prefissarlo per typename per dire al compilatore di analizzarlo in un certo modo. Lo standard dice in (14.6 / 2):

    Si presume che un nome utilizzato in una dichiarazione o una definizione di modello e dipendente da un parametro del modello non indichi un tipo a meno che la ricerca del nome applicabile non trovi un nome di tipo o il nome sia qualificato dalla parola chiave typename.

    Ci sono molti nomi per i quali typename non è necessario, perché il compilatore può, con la ricerca del nome applicabile nella definizione del template, capire come analizzare un costrutto stesso – per esempio con T *f; , quando T è un parametro del modello di tipo. Ma per t::x * f; per essere una dichiarazione, deve essere scritto come typename t::x *f; . Se si omette la parola chiave e il nome viene considerato come non-type, ma quando l’istanzazione rileva che è un tipo, i normali messaggi di errore vengono emessi dal compilatore. A volte, l’errore di conseguenza viene dato al momento della definizione:

     // 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::xf; 

    La syntax consente il nome typename solo prima dei nomi qualificati – per questo è dato per scontato che i nomi non qualificati siano sempre noti per riferirsi ai tipi se lo fanno.

    Un gotcha simile esiste per i nomi che denotano i modelli, come suggerito dal testo introduttivo.

    La parola chiave “modello”

    Ricorda la citazione iniziale sopra e in che modo lo standard richiede anche una gestione speciale per i modelli? Prendiamo il seguente esempio innocente:

     boost::function< int() > f; 

    Potrebbe sembrare ovvio per un lettore umano. Non così per il compilatore. Immagina la seguente definizione arbitraria di boost::function e f :

     namespace boost { int function = 0; } int main() { int f = 0; boost::function< int() > f; } 

    In realtà è un’espressione valida! Usa l’operatore minore di confrontare boost::function con zero ( int() ), e quindi usa l’operatore maggiore di confrontare il bool risultante con f . Comunque, come puoi ben sapere, boost::function nella vita reale è un modello, quindi il compilatore sa (14.2 / 3):

    Dopo la ricerca del nome (3.4) si scopre che un nome è un nome-modello, se questo nome è seguito da un < , il <è sempre preso come l'inizio di un elenco-argomento-modello e mai come un nome seguito dal meno- rispetto all'operatore.

    Ora torniamo allo stesso problema con typename . Cosa succede se non possiamo ancora sapere se il nome è un modello durante l’analisi del codice? Dovremo inserire il template immediatamente prima del nome del modello, come specificato da 14.2/4 . Questo sembra:

     t::template f(); // call a function template 

    I nomi dei modelli non possono verificarsi solo dopo un :: ma anche dopo un -> o . in un accesso di un membro della class. È necessario inserire la parola chiave anche lì:

     this->template f(); // call a function template 

    dipendenze

    Per le persone che hanno libri spessi di serie sul loro scaffale e che vogliono sapere di cosa stavo parlando, parlerò un po ‘di come questo è specificato nello Standard.

    Nelle dichiarazioni di template alcuni costrutti hanno significati diversi a seconda degli argomenti del template che si usano per creare un’istanza del modello: le espressioni possono avere tipi o valori diversi, le variabili possono avere tipi diversi o le chiamate di funzione potrebbero finire per chiamare funzioni diverse. Generalmente tali costrutti dipendono dai parametri del modello.

    Lo standard definisce con precisione le regole in base al fatto che un costrutto sia dipendente o meno. Li separa in gruppi logicamente diversi: uno prende i tipi, un altro prende espressioni. Le espressioni possono dipendere dal loro valore e / o dal loro tipo. Quindi abbiamo, con esempi tipici aggiunti:

    • Tipi dipendenti (es .: un parametro modello tipo T )
    • Espressioni dipendenti dal valore (ad esempio: un parametro modello non di tipo N )
    • Espressioni dipendenti dal tipo (es .: cast per un parametro template tipo (T)0 )

    La maggior parte delle regole sono intuitive e sono costruite in modo ricorsivo: Ad esempio, un tipo costruito come T[N] è un tipo dipendente se N è un’espressione dipendente dal valore o T è un tipo dipendente. I dettagli di ciò possono essere letti nella sezione (14.6.2/1 ) per i tipi dipendenti, (14.6.2.2) per le espressioni dipendenti dal tipo e (14.6.2.3) per le espressioni dipendenti dal valore.

    Nomi dipendenti

    Lo standard non è chiaro su cosa sia esattamente un nome dipendente . In una lettura semplice (si sa, il principio di minima sorpresa), tutto ciò che definisce come un nome dipendente è il caso speciale per i nomi delle funzioni di seguito. Ma dal momento che T::x anche bisogno di essere guardato nel contesto dell’istanziazione, deve anche essere un nome dipendente (fortunatamente, a metà del C ++ 14 il comitato ha iniziato a esaminare come risolvere questa definizione confusa) .

    Per evitare questo problema, ho fatto ricorso a una semplice interpretazione del testo standard. Di tutti i costrutti che denotano tipi o espressioni dipendenti, un sottoinsieme di essi rappresenta nomi. Questi nomi sono quindi “nomi dipendenti”. Un nome può assumere forms diverse: lo standard dice:

    Un nome è un uso di un identificatore (2.11), ID funzione dell’operatore (13.5), ID funzione di conversione (12.3.2) o modello-id (14.2) che denota un’entity framework o un’etichetta (6.6.4, 6.1)

    Un identificatore è solo una semplice sequenza di caratteri / cifre, mentre i due successivi sono l’ operator + e la forma del operator type . L’ultima forma è template-name . Tutti questi sono nomi, e per uso convenzionale nello Standard, un nome può anche includere qualificatori che dicono quale spazio dei nomi o class un nome dovrebbe essere cercato in.

    Un’espressione dipendente dal valore 1 + N non è un nome, ma N è. Il sottoinsieme di tutti i costrutti dipendenti che sono nomi è chiamato nome dipendente . I nomi delle funzioni, tuttavia, possono avere un significato diverso nelle diverse istanze di un modello, ma sfortunatamente non sono presi da questa regola generale.

    Nomi di funzioni dipendenti

    Non è principalmente una preoccupazione di questo articolo, ma vale comunque la pena menzionarlo: i nomi delle funzioni sono un’eccezione che viene gestita separatamente. Un nome di funzione identificatore non dipende da solo, ma dalle espressioni argomento dipendenti dal tipo utilizzate in una chiamata. Nell’esempio f((T)0) , f è un nome dipendente. Nello standard, questo è specificato in (14.6.2/1) .

    Ulteriori note ed esempi

    In casi sufficienti abbiamo bisogno sia di typename che di template . Il tuo codice dovrebbe apparire come il seguente

     template  struct UnionNode : public Tail { // ... template struct inUnion { typedef typename Tail::template inUnion dummy; }; // ... }; 

    Il template parola chiave non deve sempre apparire nell’ultima parte di un nome. Può apparire nel mezzo prima del nome di una class che è usato come scope, come nell’esempio seguente

     typename t::template iterator::value_type v; 

    In alcuni casi, le parole chiave sono vietate, come descritto di seguito

    • Sul nome di una class base dipendente non è consentito scrivere nome typename . Si presume che il nome dato sia un nome di tipo class. Questo vale per entrambi i nomi nell’elenco della class base e dell’elenco di inizializzazione del costruttore:

        template  struct derive_from_Has_type : /* typename */ SomeBase::type { }; 
    • In use-declaration non è ansible usare template dopo l’ultimo :: e il comitato C ++ ha detto di non lavorare su una soluzione.

        template  struct derive_from_Has_type : SomeBase { using SomeBase::template type; // error using typename SomeBase::type; // typename *is* allowed }; 

    C ++ 11

    Problema

    Mentre le regole in C ++ 03 su quando hai bisogno di typename e template sono ampiamente ragionevoli, c’è un fastidioso svantaggio della sua formulazione

     template struct A { typedef int result_type; void f() { // error, "this" is dependent, "template" keyword needed this->g(); // OK g(); // error, "A" is dependent, "typename" keyword needed A::result_type n1; // OK result_type n2; } template void g(); }; 

    Come si può vedere, abbiamo bisogno della parola chiave disambiguazione anche se il compilatore riuscisse a capire perfettamente che A::result_type può essere solo int (ed è quindi un tipo), e this->g può essere solo il template membro g dichiarato in seguito (anche se A è esplicitamente specializzato da qualche parte, ciò non influenzerebbe il codice all’interno di quel modello, quindi il suo significato non può essere influenzato da una successiva specializzazione di A !).

    Istanza corrente

    Per migliorare la situazione, in C ++ 11 la lingua tiene traccia quando un tipo fa riferimento al modello allegato. Per saperlo, il tipo deve essere stato formato usando una certa forma di nome, che è il suo nome (in A , A , ::A ). Un tipo riferito a tale nome è noto per essere l’ istanza corrente . Potrebbero esserci più tipi che rappresentano l’istanza corrente se il tipo da cui è formato il nome è una class membro / nidificata (quindi, A::NestedClass e A sono entrambe istanze correnti).

    Sulla base di questa nozione, la lingua dice che CurrentInstantiation::Foo , Foo e CurrentInstantiationTyped->Foo (come A *a = this; a->Foo ) sono tutti membri dell’attuale istanza se si trovano membri di una class che è l’istanza corrente o una delle sue classi di base non dipendenti (eseguendo immediatamente la ricerca del nome).

    Le parole chiave typename e template ora non sono più necessarie se il qualificatore è membro dell’istanza corrente. Un punto chiave da ricordare è che A è ancora un nome dipendente dal tipo (dopo tutto T è anche dipendente dal tipo). Ma A::result_type è noto per essere un tipo – il compilatore “magicamente” esaminerà questo tipo di tipi dipendenti per capirlo.

     struct B { typedef int result_type; }; template struct C { }; // could be specialized! template struct D : B, C { 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 to provide it // But not a member of the current instantiation typename D::questionable_type r3; } }; 

    È impressionante, ma possiamo fare di meglio? Il linguaggio va anche oltre e richiede che un’implementazione guardi nuovamente su D::result_type durante l’istanziazione di D::f (anche se ha trovato il suo significato già al momento della definizione). Quando ora il risultato della ricerca differisce o si traduce in ambiguità, il programma è mal formato e deve essere fornita una diagnosi. Immagina cosa succede se abbiamo definito C come questo

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

    È richiesto un compilatore per rilevare l’errore durante l’istanziazione di D::f . In questo modo ottieni il meglio dei due mondi: la ricerca “differita” ti protegge se potresti avere problemi con le classi di base dipendenti e anche la ricerca “immediata” che ti libera da typename e template .

    Specializzazioni sconosciute

    Nel codice di D , il nome typename D::questionable_type non è un membro dell’attuale istanza. Invece la lingua lo segna come membro di una specializzazione sconosciuta . In particolare, questo è sempre il caso quando si fa DependentTypeName::Foo o DependentTypedName->Foo e il tipo dipendente non è l’istanza corrente (nel qual caso il compilatore può rinunciare e dire “guarderemo più tardi che cos’è Foo ) o è l’istanza corrente e il nome non è stato trovato in esso o le sue classi di base non dipendenti e ci sono anche classi di base dipendenti.

    Immagina cosa succede se avessimo una funzione membro h all’interno del modello di class A sopra definito

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

    In C ++ 03, il linguaggio ha permesso di catturare questo errore perché non ci sarebbe mai stato un modo valido per istanziare A::h (qualunque argomento tu dia a T ). In C ++ 11, la lingua ora ha un ulteriore controllo per dare più motivi per i compilatori di implementare questa regola. Poiché A non ha classi di base dipendenti e A non dichiara alcun tipo questionable_type , il nome A::questionable_type non è un membro dell’istanza corrente un membro di una specializzazione sconosciuta. In tal caso, non dovrebbe esserci modo che quel codice possa validamente compilare al momento dell’istanziazione, quindi la lingua proibisce un nome in cui il qualificatore è l’istanza corrente per non essere né un membro di una specializzazione sconosciuta né un membro dell’istanza corrente (tuttavia , questa violazione non è ancora richiesta per essere diagnosticata).

    Esempi e curiosità

    Puoi provare questa conoscenza su questa risposta e vedere se le definizioni di cui sopra hanno un senso per te su un esempio del mondo reale (sono ripetute leggermente meno dettagliate in quella risposta).

    Le regole del C ++ 11 rendono malformato il seguente codice C ++ 03 (che non era inteso dal comitato C ++, ma probabilmente non verrà risolto)

     struct B { void f(); }; struct A : virtual B { void f(); }; template struct C : virtual B, T { void g() { this->f(); } }; int main() { C c; cg(); } 

    Questo codice C ++ 03 valido legherebbe this->f ad A::f al momento dell’istanziazione e tutto andrà bene. C ++ 11 tuttavia lo lega immediatamente a B::f e richiede un doppio controllo durante l’istanziazione, controllando che la ricerca corrisponda ancora. Tuttavia, quando si esegue l’istanziazione di C::g , si applica la regola del dominio e la ricerca troverà invece A::f .

    PREFAZIONE

    Questo post è pensato per essere un’alternativa di facile lettura al post di litb .

    Lo scopo di fondo è lo stesso; una spiegazione a “Quando?” e perché?” typename e template devono essere applicati.


    Qual è lo scopo di typename e template ?

    typename e template sono utilizzabili in circostanze diverse da quando si dichiara un modello.

    Ci sono certi contesti in C ++ in cui al compilatore deve essere esplicitamente detto come trattare un nome, e tutti questi contesti hanno una cosa in comune; dipendono da almeno un parametro di modello .

    Ci riferiamo a tali nomi, dove può esserci un’ambiguità nell’interpretazione, come; ” nomi dipendenti “.

    Questo post offrirà una spiegazione della relazione tra nomi dipendenti e le due parole chiave.


    UN SNIPPET DICE PIÙ DI 1000 PAROLE

    Cerca di spiegare cosa sta succedendo nel seguente modello di funzione , a te stesso, a un amico o forse al tuo gatto; cosa sta succedendo nella frase contrassegnata ( A )?

     template void f_tmpl () { T::foo * x; /* < -- (A) */ } 


    Potrebbe non essere così facile come si pensa, in particolare il risultato della valutazione ( A ) dipende pesantemente dalla definizione del tipo passato come parametro-modello T

    Differenti T possono cambiare drasticamente la semantica coinvolta.

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


    I due diversi scenari :

    • Se istanziamo il modello di funzione con il tipo X , come in ( C ), avremo una dichiarazione di un puntatore a int chiamato x , ma;

    • se istanziamo il modello con il tipo Y , come in ( D ), invece ( A ) sarebbe costituito da un'espressione che calcola il prodotto di 123 moltiplicato con una variabile già dichiarata x .


    LA LOGICA

    Lo standard C ++ si preoccupa della nostra sicurezza e del nostro benessere, almeno in questo caso.

    Per evitare che un'implementazione risenta potenzialmente di brutte sorprese, lo Standard impone di risolvere l'ambiguità di un nome-dipendente dichiarando esplicitamente l'intento ovunque vorremmo trattare il nome come un nome-tipo o un modello- id .

    Se non viene indicato nulla, il nome dipendente verrà considerato come una variabile o una funzione.


    COME MANEGGIARE I NOME DIPENDENTI ?

    Se si trattava di un film di Hollywood, i nomi dipendenti sarebbero la malattia che si diffonde attraverso il contatto con il corpo, colpisce immediatamente il suo ospite per renderlo confuso. Confusione che potrebbe, eventualmente, portare a un programma perso-, erhm.

    Un nome dipendente è un nome che direttamente o indirettamente dipende da un parametro del modello .

     template void g_tmpl () { SomeTrait::type foo; // (E), ill-formsd SomeTrait::NestedTrait::type bar; // (F), ill-formsd foo.data (); // (G), ill-formsd } 

    Abbiamo quattro nomi dipendenti nello snippet sopra riportato:

    • E )
      • "type" dipende SomeTrait di SomeTrait , che include T , e;
    • F )
      • "NestedTrait" , che è un id-template , dipende da SomeTrait , e;
      • "type" alla fine di ( F ) dipende da NestedTrait , che dipende da SomeTrait , e;
    • G )
      • "data" , che assomiglia a un modello di funzione membro , è indirettamente un nome dipendente poiché il tipo di pippo dipende SomeTrait di SomeTrait .

    Nessuna delle affermazioni ( E ), ( F ) o ( G ) è valida se il compilatore interpretasse i nomi dipendenti come variabili / funzioni (che come affermato in precedenza è ciò che accade se non diciamo esplicitamente altrimenti).

    LA SOLUZIONE

    Per fare in modo che g_tmpl abbia una definizione valida dobbiamo dire esplicitamente al compilatore che ci aspettiamo un type in ( E ), un template-id e un type in ( F ), e un template-id in ( G ).

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

    Ogni volta che un nome indica un tipo, tutti i nomi coinvolti devono essere nomi di tipo o spazi dei nomi , con questo in mente è abbastanza facile vedere che applichiamo typename all'inizio del nostro nome completo.

    template , tuttavia, è diverso in questo senso, poiché non c'è modo di giungere a una conclusione come; "oh, questo è un modello, che quest'altra cosa deve essere anche un modello" . Ciò significa che applichiamo il template direttamente davanti a qualsiasi nome che vorremmo trattare come tale.


    POSSO SOLO APPARE LE PAROLE CHIAVE DAVANTI A QUALSIASI NOME?

    " Posso semplicemente attaccare typename e template davanti a qualsiasi nome? Non voglio preoccuparmi del contesto in cui appaiono ... " - Some C++ Developer

    Le regole dello Standard affermano che è ansible applicare le parole chiave purché si tratti di un nome qualificato ( K ), ma se il nome non è qualificato l'applicazione è mal formata ( L ).

     namespace N { template struct X { }; } 

      N:: X a; // ... legal typename N::template X b; // (K), legal typename template X c; // (L), ill-formsd 

    Nota : l'applicazione di typename o template in un contesto in cui non è richiesto non è considerata una buona pratica; solo perché puoi fare qualcosa, non significa che dovresti.

    Inoltre ci sono contesti in cui typename e template sono esplicitamente non consentiti:

    • Quando si specificano le basi di cui una class eredita

      Ogni nome scritto nella lista degli specificatori di base di una class derivata è già trattato come un nome-tipo , specificando esplicitamente che typename è sia mal formato che ridondante.

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

    • Quando l' id-template è quello a cui si fa riferimento nella direttiva using di una class derivata

        struct Base { template struct type { }; }; struct Derived : Base { using Base::template type; // ill-formsd using Base::type; // legal }; 
     typedef typename Tail::inUnion dummy; 

    Tuttavia, non sono sicuro che l’implementazione di inUnion sia corretta. Se ho capito bene, questa class non dovrebbe essere istanziata, quindi la scheda “fallire” non fallirà mai. Forse sarebbe meglio indicare se il tipo è nel sindacato o meno con un semplice valore booleano.

     template  struct Contains; template  struct Contains > { enum { result = Contains::result }; }; template  struct Contains > { enum { result = true }; }; template  struct Contains { enum { result = false }; }; 

    PS: dai un’occhiata a Boost :: Variant

    PS2: dai un’occhiata alle liste di caratteri , in particolare nel libro di Andrei Alexandrescu: Modern C ++ Design

    Questa risposta vuole essere piuttosto corta e dolce per rispondere (in parte) alla domanda intitolata. Se vuoi una risposta con più dettagli che spieghi perché devi metterli lì, per favore vai qui .


    La regola generale per inserire la parola chiave typename è principalmente quando si utilizza un parametro del modello e si desidera accedere a un typedef annidato oa un alias di utilizzo, ad esempio:

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

    Nota che questo vale anche per le meta-funzioni o per le cose che richiedono anche parametri generici per i modelli. Tuttavia, se il parametro del template fornito è di tipo esplicito, non è necessario specificare typename , ad esempio:

     template struct test { // typename required using type = typename std::conditional::type; // no typename required using integer = std::conditional::type; }; 

    Le regole generali per l’aggiunta del qualificatore del template sono per lo più simili, tranne che in genere coinvolgono le funzioni dei membri basate su modelli (statiche o meno) di una struttura / class che è a sua volta basata su modelli, ad esempio:

    Data questa struttura e funzione:

     template struct test { template void get() const { std::cout < < "get\n"; } }; template void func(const test& t) { t.get(); // error } 

    Il tentativo di accedere a t.get() dall’interno della funzione provocherà un errore:

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

    Quindi in questo contesto avresti bisogno in anticipo della parola chiave template e la chiamerai in questo modo:

    t.template get()

    In questo modo il compilatore analizzerà questo correttamente piuttosto che t.get < int .

    Sto ponendo l’eccellente risposta di JLBorges a una domanda simile testualmente da cplusplus.com, in quanto è la spiegazione più succinta che ho letto sull’argomento.

    In un modello che scriviamo, ci sono due tipi di nomi che potrebbero essere usati: nomi dipendenti e nomi non dipendenti. Un nome dipendente è un nome che dipende da un parametro del modello; un nome non dipendente ha lo stesso significato indipendentemente da quali siano i parametri del template.

    Per esempio:

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

    A cosa si riferisce un nome dipendente potrebbe essere qualcosa di diverso per ogni diversa istanziazione del modello. Di conseguenza, i modelli C ++ sono soggetti alla “ricerca del nome in due fasi”. Quando un modello viene inizialmente analizzato (prima che venga eseguita un’istanza), il compilatore cerca i nomi non dipendenti. Quando si verifica una particolare istanziazione del modello, i parametri del modello sono noti a quel punto e il compilatore cerca i nomi dipendenti.

    Durante la prima fase, il parser deve sapere se un nome dipendente è il nome di un tipo o il nome di un non-tipo. Per impostazione predefinita, si presuppone che il nome dipendente sia il nome di un non-tipo. La parola chiave typename prima di un nome dipendente specifica che si tratta del nome di un tipo.


    Sommario

    Utilizzare la parola chiave typename solo nelle dichiarazioni e nelle definizioni del modello, purché si disponga di un nome qualificato che faccia riferimento a un tipo e che dipenda da un parametro del modello.