2010-05-11 10 views
16
template<typename T> 
class Base 
{ 
protected: 
    Base() {} 
    T& get() { return t; } 
    T t; 
}; 

template<typename T> 
class Derived : public Base<T> 
{ 
public: 
    Base<T>::get;     // Line A 
    Base<T>::t;      // Line B 
    void foo() { t = 4; get(); } 
}; 

int main() { return 0; } 

Se commento fuori le linee A e B, questo codice compila bene in Visual Studio 2008. Eppure, quando compilo sotto GCC 4.1 con le linee A e B ha commentato, ottengo questi errori:Perché GCC necessita di dichiarazioni extra nei modelli quando VS non lo fa?

In member function ‘void Derived::foo()’:
error: ‘t’ was not declared in this scope
error: there are no arguments to ‘get’ that depend on a template parameter, so a declaration of ‘get’ must be available

Perché un compilatore richiederebbe le linee A e B mentre l'altro no? C'è un modo per semplificare questo? In altre parole, se le classi derivate usano 20 cose dalla classe base, devo mettere 20 righe di dichiarazioni per ogni classe derivante da Base! C'è un modo per aggirare questo che non richiede così tante dichiarazioni?

+6

Un collegamento obbligatorio per C++ FAQ: http://www.parashift.com/c++-faq- lite/templates.html # faq-35.19 – UncleBens

+4

Risposta breve: perché gcc è conforme agli standard e (sorprendentemente) Visual C++ non lo è? –

risposta

15

GCC ha ragione in questo caso e Visual Studio accetta erroneamente un programma non valido. Dai un'occhiata alla sezione Name lookup nel manuale GCC. Parafrasando:

[T]he call to [ get() ] is not dependent on template arguments (there are no arguments that depend on the type T, and it is also not otherwise specified that the call should be in a [template-]dependent context). Thus a global declaration of such a function must be available, since the one in the base class is not visible until instantiation time.

È possibile ottenere intorno a questo in uno dei tre modi:

  • Le dichiarazioni si sta già utilizzando.
  • Base<T>::get()
  • this->get()

(c'è anche un quarto modo, se si vuole soccombere al lato oscuro:

Using the -fpermissive flag will also let the compiler accept the code, by marking all function calls for which no declaration is visible at the time of definition of the template for later lookup at instantiation time, as if it were a dependent call. We do not recommend using -fpermissive to work around invalid code, and it will also only catch cases where functions in base classes are called, not where variables in base classes are used (as in the example above).

Ma vorrei raccomandare contro che, sia per la ragione menzionato nel manuale, e per il motivo che il tuo codice sarà ancora valido C++.)

+0

"erroneamente" non è corretto. Se disabiliti le estensioni non standard in Visual C++ (che dovresti fare, se vuoi usarlo per scrivere codice portatile), ti avviserà che il tuo codice usa questa estensione. –

+1

No, queste estensioni sono migliori dello "standard" poco brillante. – HardCoder

5

Il problema non è con gcc ma con Visual Studio, w accetta il codice che non è conforme allo standard C++.

E 'stato risposto in tutto e per tutto su questo sito così sarò breve.

La norma richiede modelli da valutare due volte:

  • una volta al punto di definizione: template <class T> struct Foo { void bar(); };
  • una volta al punto di instanciation: Foo<int> myFoo;

La prima volta, tutte le Nomi non dipendenti sono deducibili dal contesto:

  • il compilatore vomitare se hai dimenticato la punteggiatura, si riferisce a sconosciuti tipi/metodi/attributi
  • il compilatore selezionerà il sovraccarico per le funzioni interessate a questo punto

perché la sintassi C++ è ambiguo, è necessario per aiutare il parser in questa fase e utilizzare le parole chiave template e typename in modo appropriato per disambiguare manualmente.

Sfortunatamente, Visual Studio non è conforme e implementa solo la seconda valutazione (al punto di instanciazione).Il vantaggio per i più pigri è che si può ottenere via senza quelli extra template e typename parole chiave, lo svantaggio è che il codice è mal formata e non portatili ...

Ora per la parte divertente:

void foo(int) { std::cout << "int" << std::endl; } 

template <class T> void tfoo(T i) { foo(i); } 

void foo(double) { std::cout << "double" << std::endl; } 

int main(int argc, char* argv[]) 
{ 
    double myDouble = 0.0; 
    tfoo(myDouble); 
    return 0; 
} 

Compilato con gcc, emette int.

Compilato con Visual Studio, emette double.

Il problema? Chiunque riutilizzi lo stesso simbolo che fai nel tuo codice template in VS potrebbe fare un passo falso alla tua implementazione se il loro simbolo appare tra l'inclusione del tuo codice template e il momento in cui effettivamente usano il codice template ... non è vero? divertente :/ ?

Ora, per il codice:

template<typename T> 
class Derived : public Base<T> 
{ 
public: 
    void foo() { this->t = 4; this->get(); } 
}; 

Il this indica che il nome che segue è un nome dipendente, vale a dire che dipende da T (che non è evidente quando il simbolo appare solo). Il compilatore attenderà quindi l'instanciazione e vedrà se per il particolare tipo che installi il modello Base<T> contiene quei metodi. Non è obbligatorio, da quando ho potuto specializzarmi perfettamente Base:

// It's non-sensical to instanciate a void value, 
template <> 
class Base<void> {}; 

E così Derived<void> non dovrei compilare;)

Problemi correlati