(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 t
mezzo. 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::x
riferisce un nome , se si t
riferisce a un parametro del tipo di modello? x
potrebbe 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 typename
per 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 typename
non è 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 typename
solo 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::function
e 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::function
rispetto a zero ( int()
), quindi utilizza l'operatore maggiore di per confrontare il risultante bool
con 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 template
immediatamente 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::x
necessario 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 + N
non è 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 typename
e 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 template
non 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 template
dopo 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
};