2010-05-11 4 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; } 

Wenn ich Linien A und B auf Kommentar, dieser Code kompiliert unter Visual Studio 2008. Und doch gut, wenn ich unter GCC kompilieren 4.1 mit Linien A und B kommentiert, erhalte ich diese Fehler:Warum benötigt GCC zusätzliche Deklarationen in Vorlagen, wenn VS dies nicht tut?

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

Warum würde ein Compiler die Zeilen A und B benötigen, während der andere nicht? Gibt es eine Möglichkeit, dies zu vereinfachen? Mit anderen Worten, wenn abgeleitete Klassen 20 Dinge aus der Basisklasse verwenden, muss ich für jede Klasse, die von Base stammt, 20 Deklarationszeilen setzen. Gibt es dafür einen Weg, der nicht so viele Deklarationen erfordert?

+6

Ein obligatorischer Link zu C++ FAQ: http://www.parashift.com/c++-faq- lite/templates.html # faq-35.19 – UncleBens

+4

Kurze Antwort: weil gcc standardkonform ist und (überraschend) Visual C++ nicht ist? –

Antwort

15

GCC ist in diesem Fall richtig, und Visual Studio akzeptiert fälschlicherweise ein fehlerhaftes Programm. Werfen Sie einen Blick auf den Abschnitt Name lookup im GCC-Handbuch. Paraphrasieren:

[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.

Sie um diese in einer von drei Wegen:

  • Die Erklärungen Sie bereits verwenden.
  • Base<T>::get()
  • this->get()

(Es gibt auch eine vierte Möglichkeit, wenn Sie auf die dunklen Seite erliegen wollen:

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

Aber ich würde dagegen empfehlen, die beide aus dem Grunde, im Handbuch erwähnt, und aus dem Grund, dass Ihr Code immer noch ungültig C++ ist.)

+0

"irrtümlich" ist falsch. Wenn Sie nicht standardmäßige Erweiterungen in Visual C++ deaktivieren (was Sie tun sollten, wenn Sie es verwenden möchten, um portablen Code zu schreiben), werden Sie gewarnt, dass Ihr Code diese Erweiterung verwendet. –

+1

Nein, diese Erweiterungen sind besser als der glanzlose "Standard". – HardCoder

5

Das Problem ist nicht mit gcc, sondern mit Visual Studio, w hich akzeptiert Code, der nicht dem C++ Standard entspricht.

Es wurde auf dieser Seite durch und durch beantwortet, also werde ich mich kurz fassen.

  • einmal an dem Punkt der Definition:

    Die Standardvorlagen erfordert zweimal ausgewertet werden template <class T> struct Foo { void bar(); };

  • einmal an dem Punkt Instanziierungsmodus: Foo<int> myFoo;

Das erste Mal, die ganze nicht abhängige Namen sind vom Kontext abzugsfähig:

  • der Compiler erbrechen, wenn Sie Interpunktion vergessen haben, bezieht sich auf unbekannte Arten/Methoden/Attribute
  • der Compiler die Überlastung für die Funktionen an dieser Stelle

Da C++ beteiligt wählen Syntax nicht eindeutig ist, ist es notwendig, dem Parser in dieser Phase zu helfen und die Schlüsselwörter template und typename entsprechend zu verwenden, um manuell zu disambiguieren.

Leider ist Visual Studio nicht kompatibel und implementiert nur die zweite Auswertung (zum Zeitpunkt der Instanziierung).Der Vorteil für die Faulen ist, dass man ohne diese zusätzlichen template und typename Schlüsselwörter wegkommen kann, ist der Nachteil, dass der Code ist schlecht ausgebildet und nicht tragbar ...

Nun zum spaßigen Teil:

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

Mit gcc kompiliert, gibt es int aus.

Kompiliert mit Visual Studio, gibt es double aus.

Das Problem? Jeder, der das gleiche Symbol in Ihrem Vorlagencode in VS verwendet, könnte auf Ihren Fuß treten und Ihre Implementierung durcheinander bringen, wenn sein Symbol zwischen der Aufnahme Ihres Vorlagencodes und dem Moment der tatsächlichen Verwendung des Vorlagencodes erscheint ... nicht wahr? komisch :/ ?

nun für Ihren Code:

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

Die this zeigt an, dass der Name folgt eine Abhängiger Name, dh sie auf T abhängt (was nicht offensichtlich ist, wenn das Symbol alleine erscheint). Der Compiler wird daher auf die Instantiierung warten und sehen, ob die Vorlage Base<T> für diesen bestimmten Typ die Methoden enthält. Es ist nicht zwingend notwendig, da ich vollkommen Base spezialisieren konnte:

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

Und so Derived<void> sollte nicht kompilieren;)

Verwandte Themen