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


1127

Nei modelli, dove e perché devo mettere typename e templatesui nomi di dipendenti?
Che cosa sono esattamente i nomi dipendenti comunque?

Ho il codice seguente:

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> {
    };
};

Il problema che ho è nella typedef Tail::inUnion<U> dummylinea. Sono abbastanza sicuro che inUnionsia un nome dipendente e VC ++ ha ragione a soffocarlo.
So anche che dovrei essere in grado di aggiungere templateda qualche parte per dire al compilatore che inUnion è un template-id. Ma dove esattamente? E dovrebbe quindi supporre che inUnion sia un modello di classe, ovvero inUnion<U>nomina un tipo e non una funzione?


1
Domanda fastidiosa: perché non aumentare :: Variante?
Assaf Lavie,

58
Sensibilità politica, portabilità.
MSalters

5
Ho messo in risalto la tua vera domanda ("Dove mettere template / typename?") Mettendo la domanda e il codice finali all'inizio e abbreviato il codice in orizzontale per adattarlo a uno schermo 1024x.
Johannes Schaub - litb

7
Rimossi i "nomi dipendenti" dal titolo perché sembra che la maggior parte delle persone che si chiedono "nome tipografico" e "modello" non sappiano quali siano "nomi dipendenti". Dovrebbe essere meno confuso per loro in questo modo.
Johannes Schaub - litb

2
@MSalters: boost è abbastanza portatile. Direi che solo la politica è il motivo generale per cui la spinta è spesso senza ostacoli. L'unica buona ragione che conosco è l'aumento dei tempi di costruzione. Altrimenti si tratta di perdere migliaia di dollari reinventando la ruota.
v.oddou,

Risposte:


1165

(Vedi anche qui per la mia risposta C ++ 11 )

Per analizzare un programma C ++, il compilatore deve sapere se alcuni nomi sono tipi o no. L'esempio seguente dimostra che:

t * f;

Come dovrebbe essere analizzato? Per molte lingue un compilatore non ha bisogno di conoscere il significato di un nome per analizzare e fondamentalmente sapere quale azione fa una riga di codice. In C ++, tuttavia, quanto sopra può produrre interpretazioni molto diverse a seconda del tmezzo. 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 indica una di queste entità prima di continuare ad analizzare il programma che lo contiene. Il processo che determina ciò si chiama ricerca del nome.

Come farà il compilatore a scoprire a cosa si t::xriferisce un nome , se si triferisce a un parametro del tipo di modello? xpotrebbe essere un membro di dati int statici che potrebbe essere moltiplicato o potrebbe anche essere una classe nidificata o un typedef che potrebbe produrre una dichiarazione. Se un nome ha questa proprietà - che non può essere cercata fino a quando non si conoscono gli argomenti del modello reale - allora si chiama un nome dipendente ("dipende" dai parametri del modello).

Si consiglia di attendere fino a quando l'utente non crea un'istanza del modello:

Aspettiamo fino a quando l'utente non crea un'istanza del modello e poi scopriamo il vero significato di t::x * f;.

Ciò funzionerà e in realtà è consentito dallo Standard come possibile 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 possibilmente rilevano errori nella definizione. Ma invece di infastidire gli utenti del modello (poveri colleghi!) Con errori commessi dall'autore di un modello, altre implementazioni scelgono di controllare i modelli in anticipo e dare errori nella definizione il prima possibile, prima ancora che avvenga un'istanza.

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

La parola chiave "typename"

La risposta è: Noi decidiamo come il compilatore dovrebbe analizzare questo. Se t::xè un nome dipendente, allora dobbiamo aggiungere un prefisso typenameper dire al compilatore di analizzarlo in un certo modo. Lo standard dice a (14.6 / 2):

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

Esistono molti nomi per i quali typenamenon è necessario, poiché il compilatore può, con la ricerca del nome applicabile nella definizione del modello, capire come analizzare un costrutto stesso, ad esempio con T *f;quando Tè un parametro del modello di tipo. Ma per t::x * f;essere una dichiarazione, deve essere scritta come typename t::x *f;. Se si omette la parola chiave e si considera che il nome non è un tipo, ma quando l'istanza trova che indica un tipo, i compilatori generano i soliti messaggi di errore. 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::x f;

La sintassi consente typenamesolo prima di nomi qualificati - è quindi scontato che i nomi non qualificati siano sempre noti per fare riferimento a tipi se lo fanno.

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

La parola chiave "modello"

Ricorda la citazione iniziale sopra e come lo Standard richiede una gestione speciale anche per i template? Facciamo il seguente esempio dall'aspetto innocente:

boost::function< int() > f;

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

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

Questa è in realtà un'espressione valida ! Utilizza l'operatore minore di per confrontare boost::functionrispetto a zero ( int()), quindi utilizza l'operatore maggiore di per confrontare il risultante boolcon f. Tuttavia, come potresti ben sapere, boost::function nella vita reale è un modello, quindi il compilatore sa (14.2 / 3):

Dopo che la ricerca del nome (3.4) rileva che un nome è un nome-modello, se questo nome è seguito da un <, il <viene sempre considerato come l'inizio di un elenco di argomenti-modello e mai come un nome seguito dal meno- dell'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 templateimmediatamente prima del nome del modello, come specificato da 14.2/4. Questo sembra:

t::template f<int>(); // call a function template

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

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

dipendenze

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

Nelle dichiarazioni dei modelli alcuni costrutti hanno significati diversi a seconda degli argomenti dei modelli utilizzati 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. Si dice che tali costrutti dipendono dai parametri del modello.

Lo standard definisce precisamente le regole in base alla dipendenza o meno di un costrutto. Li separa in gruppi logicamente diversi: uno cattura i tipi, l'altro cattura le espressioni. Le espressioni possono dipendere dal loro valore e / o dal loro tipo. Quindi abbiamo aggiunto esempi tipici:

  • Tipi dipendenti (ad es. Un parametro modello tipo T)
  • Espressioni dipendenti dal valore (ad es. Un parametro modello non tipo N)
  • Espressioni dipendenti dal tipo (ad es. Un cast su un parametro del modello di tipo (T)0)

La maggior parte delle regole sono intuitive e 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 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 è abbastanza chiaro su cosa sia esattamente un nome dipendente . Su una semplice lettura (sai, il principio della minima sorpresa), tutto ciò che definisce come un nome dipendente è il caso speciale per i nomi delle funzioni di seguito. Ma dal momento che è chiaramente T::xnecessario cercare nel contesto dell'istanza, deve anche essere un nome dipendente (fortunatamente, a partire dalla 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 i nomi. Tali nomi sono quindi "nomi dipendenti". Un nome può assumere forme diverse: lo standard dice:

Un nome è un uso di un identificatore (2.11), id-funzione-operatore (13.5), id-funzione-conversione (12.3.2) o id-modello (14.2) che indica un'entità o un'etichetta (6.6.4, 6.1)

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

Un'espressione dipendente dal valore 1 + Nnon è un nome, ma lo 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 riguarda principalmente questo articolo, ma vale comunque la pena menzionarlo: i nomi delle funzioni sono un'eccezione gestita separatamente. Il nome di una funzione identificatore dipende non da solo, ma dalle espressioni di 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) .

Note ed esempi aggiuntivi

In casi sufficienti abbiamo bisogno di entrambi typenamee template. Il codice dovrebbe essere simile al seguente

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

La parola chiave templatenon deve sempre apparire nell'ultima parte di un nome. Può apparire nel mezzo prima di un nome di classe utilizzato come ambito, come nell'esempio seguente

typename t::template iterator<int>::value_type v;

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

  • Sul nome di una classe base dipendente non è consentito scrivere typename. Si presume che il nome fornito sia un nome di tipo di classe. Questo vale per entrambi i nomi nell'elenco delle classi di base e nell'elenco di inizializzatori del costruttore:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
  • Nelle dichiarazioni di utilizzo non è possibile utilizzare templatedopo l'ultimo ::e il comitato C ++ ha dichiarato di non lavorare su una soluzione.

     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
Questa risposta è stata copiata dalla mia precedente voce FAQ che ho rimosso, perché ho scoperto che avrei dovuto usare meglio domande simili esistenti invece di creare nuove "pseudo domande" solo allo scopo di rispondere. Grazie a @Prasoon , che ha modificato le idee dell'ultima parte (casi in cui il nome / modello è vietato) nella risposta.
Johannes Schaub - litb,

1
Potete aiutarmi quando dovrei usare questa sintassi? this-> template f <int> (); Ottengo questo errore 'template' (come disambiguator) è consentito solo all'interno dei template ma senza la parola chiave template, funziona benissimo.
Balki,

1
Oggi ho posto una domanda simile, che presto è stata contrassegnata come duplicata: stackoverflow.com/questions/27923722/… . Mi è stato chiesto di rianimare questa domanda invece di crearne una nuova. Devo dire che non sono d'accordo sul fatto che siano duplicati, ma chi sono io, giusto? Quindi, c'è qualche motivo che typenameviene applicato anche quando la sintassi non consente interpretazioni alternative a parte i nomi dei tipi a questo punto?
JorenHeit,

1
@Pablo non ti manca nulla. Ma è ancora necessario scrivere la disambiguazione anche se la riga completa non sarebbe più ambigua.
Johannes Schaub - litb

1
@Pablo lo scopo è di semplificare la lingua e i compilatori. Ci sono proposte per consentire a più situazioni di capire automaticamente le cose, in modo che tu abbia bisogno della parola chiave meno spesso. Nota che nel tuo esempio, il token è ambiguo e solo dopo aver visto ">" dopo il doppio, puoi disambiguirlo come parentesi angolare del modello. Per ulteriori dettagli, sono la persona sbagliata da chiedere, perché non ho esperienza nell'implementazione di un parser di compilatori C ++.
Johannes Schaub - litb,

136

C ++ 11

Problema

Mentre le regole in C ++ 03 su quando hai bisogno typenamee templatesono in gran parte ragionevoli, c'è un fastidioso svantaggio della sua formulazione

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

Come si può vedere, abbiamo bisogno della parola chiave di disambiguazione anche se il compilatore potrebbe capire perfettamente se stesso che A::result_typepuò essere int(e quindi è un tipo), e this->gpuò essere solo il modello membro gdichiarato in seguito (anche se Aè esplicitamente specializzato da qualche parte, che sarebbe non influisce sul codice all'interno di quel modello, quindi il suo significato non può essere influenzato da una specializzazione successiva di A!).

Istanziazione attuale

Per migliorare la situazione, in C ++ 11 la lingua segue quando un tipo fa riferimento al modello allegato. Per sapere che il carattere deve essersi formato utilizzando una certa forma di nome, che è il suo nome (in quanto sopra, A, A<T>, ::A<T>). Un tipo a cui fa riferimento un nome simile è noto come istanza corrente . Possono esserci più tipi che rappresentano tutta l'istanza corrente se il tipo da cui viene formato il nome è un membro / classe nidificata (quindi, A::NestedClasseA sono entrambi istanze correnti).

Sulla base di questa nozione, il linguaggio dice che CurrentInstantiation::Foo, Fooe CurrentInstantiationTyped->Foo(come A *a = this; a->Foo) sono tutti membri dell'istanza corrente se vengono trovati membri di una classe che è l'istanza corrente o una delle sue classi base non dipendenti (semplicemente facendo la ricerca del nome immediatamente).

Le parole chiave typenamee templateora non sono più necessarie se il qualificatore è un membro dell'istanza corrente. Un punto chiave qui da ricordare è che A<T>è ancora un nome dipendente dal tipo (dopo tutto Tdipende anche dal tipo). Ma A<T>::result_typeè noto per essere un tipo: il compilatore esaminerà "magicamente" questo tipo di tipi dipendenti per capirlo.

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

È impressionante, ma possiamo fare di meglio? Il linguaggio va anche oltre e richiede che un'implementazione guardi di nuovo D::result_typedurante l'istanza D::f(anche se ha trovato il suo significato già al momento della definizione). Quando ora il risultato della ricerca differisce o provoca ambiguità, il programma è mal formato e deve essere fornita una diagnostica. Immagina cosa succede se abbiamo definito Ccosì

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

È necessario un compilatore per rilevare l'errore durante l'istanza D<int>::f. In questo modo ottieni il meglio dei due mondi: la ricerca "ritardata" ti protegge se potresti avere problemi con le classi di base dipendenti e anche la ricerca "immediata" che ti libera da typenamee template.

Specializzazioni sconosciute

Nel codice di D, il nome typename D::questionable_typenon è un membro dell'istanza corrente. Invece il linguaggio lo contrassegna come membro di una specializzazione sconosciuta . In particolare, questo è sempre il caso quando stai facendo DependentTypeName::Fooo DependentTypedName->Fooe o il tipo dipendente non è l'istanza corrente (nel qual caso il compilatore può rinunciare e dire "vedremo più avanti quello che Fooè) o è l'istanza corrente e il il nome non è stato trovato in esso o nelle sue classi base non dipendenti e ci sono anche classi base dipendenti.

Immagina cosa succede se avessimo una funzione membro hall'interno del Amodello di classe sopra definito

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

In C ++ 03, il linguaggio ha permesso di rilevare questo errore perché non ci sarebbe mai stato un modo valido per istanziare A<T>::h(qualunque argomento tu dia T). In C ++ 11, il linguaggio ora ha un ulteriore controllo per fornire maggiori ragioni ai compilatori per implementare questa regola. Poiché Anon ha classi di base dipendenti e Anon dichiara alcun membro questionable_type, il nome nonA<T>::questionable_type è un membro dell'istanza corrente un membro di una specializzazione sconosciuta. In tal caso, non dovrebbe essere possibile compilare validamente quel codice al momento dell'istanza, quindi la lingua proibisce a un nome in cui il qualificatore è l'istanza corrente di non essere né un membro di una specializzazione sconosciuta né un membro dell'istanza corrente (tuttavia , non è ancora necessario diagnosticare questa violazione).

Esempi e curiosità

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

Le regole C ++ 11 rendono il seguente codice C ++ 03 valido mal formato (che non era previsto dal comitato C ++, ma probabilmente non verrà risolto)

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

Questo codice C ++ 03 valida legherebbe this->fa A::fin fase di istanziazione e tutto va bene. C ++ 11 tuttavia lo lega immediatamente B::fe richiede un doppio controllo durante l'istanza, verificando se la ricerca corrisponde ancora. Tuttavia, quando si crea un'istanza C<A>::g, si applica la regola del dominio e la ricerca troverà A::finvece.


a proposito - questa risposta è referenziata qui: stackoverflow.com/questions/56411114/… Gran parte del codice in questa risposta non viene compilato su vari compilatori.
Adam Rackis,

@AdamRack suppone che le specifiche C ++ non siano cambiate dal 2013 (data in cui ho scritto questa risposta), quindi i compilatori con cui hai provato il tuo codice semplicemente non implementano ancora questa funzionalità C ++ 11 +.
Johannes Schaub - litb

100

PREFAZIONE

Questo post è pensato per essere un'alternativa di facile lettura al post di Litb .

Lo scopo sottostante è lo stesso; una spiegazione a "Quando?" e perché?" typenamee templatedeve essere applicato.


Qual è lo scopo di typenamee template?

typenamee templatesono utilizzabili in circostanze diverse da quando si dichiara un modello.

Esistono alcuni contesti in C ++ in cui al compilatore deve essere esplicitamente spiegato come trattare un nome e tutti questi contesti hanno una cosa in comune; dipendono da almeno un parametro 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 dichiarazione contrassegnata ( A )?

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


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

Diversi Ts possono cambiare drasticamente la semantica coinvolta.

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


I due diversi scenari :

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

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



LA LOGICA

Lo standard C ++ ha a cuore la nostra sicurezza e il nostro benessere, almeno in questo caso.

Per evitare che un'implementazione da potenzialmente affetti da brutte sorprese, i mandati di serie che abbiamo risolvere l'ambiguità di un nome-dipendente dal esplicitamente indicando l'ovunque intento vorremmo trattare il nome sia come nome-tipo , o un template- id .

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



COME GESTIRE I NOMI DIPENDENTI ?

Se questo fosse un film di Hollywood, i nomi dipendenti sarebbero la malattia che si diffonde attraverso il contatto con il corpo, influenzando istantaneamente il suo ospite per renderlo confuso. Confusione che potrebbe, eventualmente, portare a un programma personale-, erhm .. mal formato.

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

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    
}

Abbiamo quattro nomi dipendenti nello snippet sopra:

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

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

LA SOLUZIONE

Per g_tmplavere una definizione valida dobbiamo dire esplicitamente al compilatore che ci aspettiamo un tipo in ( E ), un ID modello e un tipo in ( F ) e un ID modello in ( 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
}

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 typenameall'inizio del nostro nome completo .

templatetuttavia, è diverso a questo proposito, dal momento che non c'è modo di giungere a una conclusione come; "oh, questo è un modello, quindi anche questa altra cosa deve essere un modello" . Ciò significa che applichiamo templatedirettamente davanti a qualsiasi nome che vorremmo trattare come tale.



POSSO SOLO STAMPARE LE PAROLE CHIAVE DAVANTI A QUALSIASI NOME?

" Posso semplicemente rimanere typenamee templatedi fronte a qualsiasi nome? Non voglio preoccuparmi del contesto in cui appaiono ... " -Some C++ Developer

Le regole nella norma stabiliscono che è possibile applicare le parole chiave purché si tratti di un nome qualificato ( K ), ma se il nome non è qualificato l'applicazione non è valida ( 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

Nota : l'applicazione typenameo templatein un contesto in cui non è richiesta non è considerata una buona pratica; solo perché puoi fare qualcosa, non significa che dovresti.


Inoltre ci sono contesti in cui typenamee templatesono esplicitamente vietati:

  • Quando si specificano le basi di cui una classe eredita

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

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


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

     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;

Tuttavia, non sono sicuro che la tua implementazione di inUnion sia corretta. Se ho capito bene, questa classe non dovrebbe essere istanziata, quindi la scheda "fail" non fallirà mai avutamente. Forse sarebbe meglio indicare se il tipo è nell'unione o meno con un semplice valore booleano.

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: Dai un'occhiata a Boost :: Variant

PS2: dai un'occhiata alle dattilografie , in particolare nel libro di Andrei Alexandrescu: Modern C ++ Design


inUnion <U> verrebbe istanziato, se per esempio si provasse a chiamare Union <float, bool> :: operator = (U) con U == int. Chiama un set privato (U, inUnion <U> * = 0).
MSalters

E il lavoro con result = true / false è che avrei bisogno di boost :: enable_if <>, che è incompatibile con la nostra attuale toolchain OSX. Il modello separato è comunque una buona idea.
MSalters

Luc indica il manichino typedef Tail :: inUnion <U>; linea. che istanzerà Coda. ma non in Unione <U>. viene istanziato quando ha bisogno della sua definizione completa. ciò accade ad esempio se si prende la dimensione di o si accede a un membro (usando :: pippo). @Malters comunque, hai un altro problema:
Johannes Schaub -

-sizeof (U) non è mai negativo :) perché size_t è un tipo intero senza segno. otterrai un numero molto alto. probabilmente vuoi fare sizeof (U)> = 1? -1: 1 o simile :)
Johannes Schaub - litb

lo lascerei solo indefinito e lo dichiarerei solo: template <typename U> struct inUnion; quindi certamente non può essere istanziato. penso di averlo con la dimensione di, il compilatore può anche darti un errore anche se non lo crei, perché se sa che la dimensione di (U) è sempre> = 1 e ...
Johannes Schaub - litb

20

Questa risposta dovrebbe essere piuttosto breve e dolce per rispondere (parte della) domanda intitolata. Se vuoi una risposta con maggiori dettagli che spieghi perché devi metterli lì, per favore vai qui .


La regola generale per inserire la typenameparola chiave è principalmente quando si utilizza un parametro modello e si desidera accedere ad un typedefalias nidificato o usando, ad esempio:

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

Nota che ciò vale anche per meta-funzioni o cose che accettano anche parametri di template generici. Tuttavia, se il parametro modello fornito è un tipo esplicito, non è necessario specificare typename, ad esempio:

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

Le regole generali per l'aggiunta del templatequalificatore sono per lo più simili tranne per il fatto che coinvolgono in genere funzioni membro associate (statiche o di altro tipo) di una struttura / classe che è a sua volta basata su modelli, ad esempio:

Data questa struttura e funzione:

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
}

Tentare di accedere t.get<int>()dall'interno della funzione comporterà un errore:

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

Quindi in questo contesto avresti bisogno della templateparola chiave in anticipo e chiamala così:

t.template get<int>()

In questo modo il compilatore analizzerà questo correttamente anziché t.get < int.


2

Sto ponendo l'eccellente risposta di JLBorges a una domanda simile testualmente da cplusplus.com, in quanto è la spiegazione più concisa 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 modello; un nome non dipendente ha lo stesso significato indipendentemente dai parametri del modello.

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

Ciò a cui fa riferimento un nome dipendente potrebbe essere qualcosa di diverso per ogni diversa istanza del modello. Di conseguenza, i modelli C ++ sono soggetti alla "ricerca del nome in due fasi". Quando un modello viene inizialmente analizzato (prima che avvenga un'istanza) il compilatore cerca i nomi non dipendenti. Quando si verifica una particolare istanza del modello, i parametri del modello sono noti da allora 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 presume che un nome dipendente sia il nome di un non tipo. La parola chiave typename prima di un nome dipendente specifica che è il nome di un tipo.


Sommario

Utilizzare la parola chiave typename solo nelle dichiarazioni e nelle definizioni dei modelli, purché si disponga di un nome qualificato che si riferisce a un tipo e dipende da un parametro del modello.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.