2009-03-13 12 views
45

Era da un po 'che GCC mi ha catturato con questo, ma è successo oggi. Ma non ho mai capito perché GCC richieda typedef typename nei template, mentre VS e suppongo che ICC no. Il typedef typename è un "bug" o uno standard overstrict o qualcosa che è lasciato agli scrittori del compilatore?Perché devo usare typedef typename in g ++ ma non VS?

Per coloro che non so se mi spiego Ecco un esempio:

template<typename KEY, typename VALUE> 
bool find(const std::map<KEY,VALUE>& container, const KEY& key) 
{ 
    std::map<KEY,VALUE>::const_iterator iter = container.find(key); 
    return iter!=container.end(); 
} 

Il codice di cui sopra viene compilato in VS (e probabilmente in ICC), ma non riesce a GCC perché vuole in questo modo:

template<typename KEY, typename VALUE> 
bool find(const std::map<KEY,VALUE>& container, const KEY& key) 
{ 
    typedef typename std::map<KEY,VALUE>::const_iterator iterator; //typedef typename 
    iterator iter = container.find(key); 
    return iter!=container.end(); 
} 

Nota: Questa non è una funzione reale che sto usando, ma solo qualcosa di stupido che illustra il problema.

+4

Il motivo per cui è necessario in g ++, è perché g ++ è più conforme allo standard. VS era un po 'trascurato in questa parte dell'analisi delle templatisation (che ha portato ad altri problemi in modelli più complessi). –

+0

Sì, ma perché lo standard fa questo? Mi sono occupato di codice identico! –

risposta

52

Il nome tipo è richiesto dallo standard. La compilazione dei modelli richiede una verifica in due passaggi. Durante il primo passaggio il compilatore deve verificare la sintassi del modello senza fornire effettivamente le sostituzioni di tipo. In questo passo, si presuppone che std :: map :: iterator sia un valore. Se denota un tipo, è richiesta la parola chiave typename.

Perché è necessario? Prima di sostituire i tipi KEY e VALUE effettivi, il compilatore non può garantire che il modello non sia specializzato e che la specializzazione non stia ridefinendo la parola chiave iteratore come qualcos'altro.

Si può controllare con questo codice:

class X {}; 
template <typename T> 
struct Test 
{ 
    typedef T value; 
}; 
template <> 
struct Test<X> 
{ 
    static int value; 
}; 
int Test<X>::value = 0; 
template <typename T> 
void f(T const &) 
{ 
    Test<T>::value; // during first pass, Test<T>::value is interpreted as a value 
} 
int main() 
{ 
    f(5); // compilation error 
    X x; f(x); // compiles fine f: Test<T>::value is an integer 
} 

L'ultima chiamata ha esito negativo con un errore che indica che durante la prima fase del modello di compilazione di f() Test :: valore è stato interpretato come un valore, ma istanza di il modello Test <> con il tipo X produce un tipo.

+3

Buon esempio di caso di errore –

+2

Penso che abbiate mescolato i vostri commenti sulle due chiamate a 'f',' f (X()); 'succede mentre' f (5); 'è un errore di compilazione. In ogni caso, MSVC gestisce questo problema - sembra ritardare la decisione se 'Test :: value' sia un valore o un tipo fino a quando il modello non è stato istanziato. Tuttavia, non lo fa per i membri di un modello di classe. –

+0

@Sumudu: hai ragione, ho corretto anche la chiamata 'f (X())' nel codice più esplicito sopra. Se MSVC ritarda il controllo fino a quando il tipo viene istanziato, MSVC non è conforme allo standard. –

31

Bene, GCC in realtà non richiede il typedef - typename è sufficiente. Questo funziona:

#include <iostream> 
#include <map> 

template<typename KEY, typename VALUE> 
bool find(const std::map<KEY,VALUE>& container, const KEY& key) 
{ 
    typename std::map<KEY,VALUE>::const_iterator iter = container.find(key); 
    return iter!=container.end(); 
} 

int main() { 
    std::map<int, int> m; 
    m[5] = 10; 
    std::cout << find(m, 5) << std::endl; 
    std::cout << find(m, 6) << std::endl; 
    return 0; 
} 

Questo è un esempio di un problema di analisi sensibile al contesto. Ciò che la linea in questione significa non è evidente dalla sintassi solo in questa funzione - è necessario sapere se std::map<KEY,VALUE>::const_iterator è un tipo o meno.

Ora, non riesco a pensare ad un esempio di cosa ... ::const_iterator potrebbe essere ad eccezione di un tipo, che non sarebbe anche un errore. Quindi immagino che il compilatore possa scoprire che lo ha un come tipo, ma potrebbe essere difficile per il povero compilatore (scrittori).

Lo standard richiede l'uso di typename qui, in base alla sezione 14.6/3 della norma.

+0

Anche qui penso che intendiate KEY, VALUE nella riga che inizia con "typename". Con quel cambiamento, mi ricompensa. :) – unwind

+0

Ho visto il tuo commento sulla domanda e l'ho risolto subito :) –

+0

sì, è davvero necessario. per un riferimento vedere 14.6/3 –

4

Sembra che VS/ICC fornisca la parola chiave typename ovunque sia che sia richiesta. Nota questa è una cosa cattiva (TM) - per lasciare che il compilatore decida cosa vuoi fare . Ciò complica ulteriormente il problema instillando la cattiva abitudine di saltare il typename quando richiesto ed è un incubo portabilità. Questo non è sicuramente il comportamento standard. Prova in modalità standard rigorosa o Comeau.

+5

Sarebbe una brutta cosa se il compilatore lo avesse fatto volenti o nolenti. In effetti, lo fa solo su codice non funzionante. Non c'è in realtà alcun divieto nello standard contro la compilazione del codice rotto. Dovrebbe comunque essere un avvertimento (diagnostico). – MSalters

3

Questo è un bug nel compilatore Microsoft C++ - nel tuo esempio, std :: map :: iterator potrebbe non essere un tipo (potresti avere specializzato std :: map su KEY, VALUE in modo che std :: map: : iterator era una variabile per esempio).

GCC obbliga a scrivere codice corretto (anche se ciò che intendevi era ovvio), mentre il compilatore Microsoft indovina correttamente cosa intendevi (anche se il codice che hai scritto non era corretto).

+1

In realtà, sembra che MSVC verificherà se std :: map :: iterator è un tipo o meno prima di decidere. Non ho una copia dello standard, ma questo mi sembra un comportamento non conforme, ma significa solo che (cercherà di) correggere e compilare alcuni programmi errati, non introdurre errori in quelli corretti. –

+1

Sì, è un bug perché il compilatore non rilascia una diagnostica per codice illegale. –

+2

Non esiste il codice illegale. Una diagnostica è necessaria solo se il programma è mal formato. – Yttrill

2

Va notato che il problema di tipo/valore non è il problema fondamentale. Il problema principale è che analizza. Si consideri

template<class T> 
void f() { (T::x)(1); } 

Non c'è modo di dire se si tratta di una fusione o di una chiamata di funzione a meno che la parola typename è obbligatoria. In tal caso, il codice sopra riportato contiene una chiamata di funzione. In generale, la scelta non può essere ritardata senza rinunciare parsing del tutto, basta considerare frammento

(a)(b)(c) 

In caso non lo ricordate, il cast ha una precedenza maggiore di chiamata di funzione in C, uno dei motivi per Bjarne voleva stile funzione getta. Non è quindi possibile dire se i mezzi

(a)(b) (c) // a is a typename 

o

(a) (b)(c) // a is not a typename , b is 

o

(a)(b) (c) // neither a nor b is a typename 

dove ho inserito spazio per indicare raggruppamento sopra.

Nota anche la parola chiave "templatename" è richiesta per lo stesso motivo di "typename", non è possibile analizzare le cose senza conoscerne il tipo in C/C++.

+0

MSVC utilizza una soluzione incredibilmente semplice a questo problema: non analizza il codice all'interno di una funzione modello fino a quando il modello non viene istanziato con una specifica T. IMO che è una soluzione molto più piacevole per gli sviluppatori di richiedere molto extra "this->" , le parole chiave "typename" e "template" e molti altri typedef aggiuntivi per ridefinire i nomi che sono * già definiti * in una classe base. (Sì, sì, lo so, MSVC non è standard, ma è più facile da usare.) – Qwertie

+0

Tuttavia, questo comportamento espone alla possibilità che la semantica di due distinte istanze sia più diversa rispetto alle già esistenti regole C++. – Yttrill

+0

Vero, ma dopo aver passato decine di ore a convertire il mio codice modello in C++ standard corretto aggiungendo un sacco di rumore sintattico, mentre sconcertato sui messaggi di errore inutili di GCC (alcuni dei miei preferiti: "dichiarazione di 'operator =' come non-funzione" , "too few template-parameter-lists", "expected primary-expression before '>' token") ... Sono venuto a detestare le regole ufficiali del C++. – Qwertie

Problemi correlati