2008-08-14 12 views
32

Der folgende Code nicht mit gcc kompilieren, aber tut mit Visual Studio:GCC Ausgabe: ein Mitglied einer Basisklasse verwenden, die auf einer Vorlage Argument hängt

template <typename T> class A { 
public: 
    T foo; 
}; 

template <typename T> class B: public A <T> { 
public: 
    void bar() { cout << foo << endl; } 
}; 

ich den Fehler:

test.cpp: In member function ‘void B::bar()’:

test.cpp:11: error: ‘foo’ was not declared in this scope

Aber es sollte sein! Wenn ich bar zu

void bar() { cout << this->foo << endl; } 

ändern dann tut Kompilierung, aber ich glaube nicht, dass ich dies zu tun. Gibt es etwas in den offiziellen Spezifikationen von C++, das GCC hier verfolgt, oder ist es nur eine Eigenart?

+0

Siehe [In einer Templates abgeleiteten Klasse, warum muss ich Basisklasse-Membernamen mit "This ->" in einer Memberfunktion qualifizieren?] (Http://stackoverflow.com/questions/7908248/in-a- templated-derived-class-why-do-i-need-to-qualify-base-class-member-name-w) – curiousguy

Antwort

10

Dies wurde in gcc-3.4 geändert. Der C++ - Parser wurde in dieser Version viel strenger - nach der Spezifikation, aber immer noch etwas nervig für Leute mit Legacy- oder Multi-Plattform-Codebasen.

18

Wow. C++ überrascht mich immer wieder mit seiner Verrücktheit.

In a template definition, unqualified names will no longer find members of a dependent base (as specified by [temp.dep]/3 in the C++ standard). For example,

template <typename T> struct B { 
    int m; 
    int n; 
    int f(); 
    int g(); 
}; 
int n; 
int g(); 
template <typename T> struct C : B<T> { 
    void h() 
    { 
    m = 0; // error 
    f(); // error 
    n = 0; // ::n is modified 
    g(); // ::g is called 
    } 
}; 

You must make the names dependent, e.g. by prefixing them with this->. Here is the corrected definition of C::h,

template <typename T> void C<T>::h() 
{ 
    this->m = 0; 
    this->f(); 
    this->n = 0 
    this->g(); 
} 

As an alternative solution (unfortunately not backwards compatible with GCC 3.3), you may use using declarations instead of this->:

template <typename T> struct C : B<T> { 
    using B<T>::m; 
    using B<T>::f; 
    using B<T>::n; 
    using B<T>::g; 
    void h() 
    { 
    m = 0; 
    f(); 
    n = 0; 
    g(); 
    } 
}; 

Das ist nur alle Arten von verrückt ist. Danke, David.

Hier ist die "temp.dep/3" der Norm [ISO/IEC 14882: 2003], dass sie sich beziehen:

In the definition of a class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member. [Example:

typedef double A; 
template<class T> class B { 
    typedef int A; 
}; 
template<class T> struct X : B<T> { 
    A a; // a has typedouble 
}; 

The type name A in the definition of X<T> binds to the typedef name defined in the global namespace scope, not to the typedef name defined in the base class B<T> . ] [Example:

struct A { 
    struct B { /* ... */ }; 
    int a; 
    int Y; 
}; 
int a; 
template<class T> struct Y : T { 
    struct B { /* ... */ }; 
    B b; //The B defined in Y 
    void f(int i) { a = i; } // ::a 
    Y* p; // Y<T> 
}; 
Y<A> ya; 

The members A::B , A::a , and A::Y of the template argument A do not affect the binding of names in Y<A> . ]

33

David Joyner hatte die Geschichte, hier ist der Grund.

Das Problem beim Kompilieren von B<T> ist, dass die Basisklasse A<T> vom Compiler unbekannt ist, da es sich um eine Schablonenklasse handelt, also keine Möglichkeit für den Compiler, Mitglieder aus der Basisklasse zu kennen.

Frühere Versionen haben einige Schlüsse gezogen, indem sie tatsächlich die Basisvorlagenklasse analysiert haben, aber ISO C++ hat festgestellt, dass diese Schlussfolgerung zu Konflikten führen kann, wo dies nicht sein sollte.

Die Lösung eine Basisklasse Mitglied in einer Vorlage zu verweisen ist this zu verwenden (wie Sie haben) oder speziell die Basisklasse nennen:

template <typename T> class A { 
public: 
    T foo; 
}; 

template <typename T> class B: public A <T> { 
public: 
    void bar() { cout << A<T>::foo << endl; } 
}; 

Weitere Informationen in gcc manual.

+8

Auf der einen Seite macht diese Art von Sinn. Aber auf der anderen Seite fühlt es sich wirklich lahm an. Der Compiler braucht * nicht * zu wissen, worauf 'foo' sich bezieht, bis die Vorlage instanziiert wird, zu welchem ​​Zeitpunkt er in der Lage sein sollte, das' foo'-Element in 'A' zu erkennen. C++ hat viel zu viele dieser seltsamen Eckfälle. –

+0

Ja, das ist völlig inakzeptabel ... kann ich nicht wissen bevor es instantiiert? dann warte wie in den Vorlagen auf die Instantiierung ... das ist der Geist, nein? was für ein Chaos ... –

8

Der Hauptgrund, warum C++ hier nichts annehmen kann, ist, dass die Basisvorlage später für einen Typ spezialisiert werden kann. Fortsetzung des ursprünglichen Beispiels:

template<> 
class A<int> {}; 

B<int> x; 
x.bar();//this will fail because there is no member foo in A<int> 
3

VC implementiert keine zweiphasige Suche, während GCC tut.Daher analysiert GCC Vorlagen, bevor sie instanziiert werden und findet daher mehr Fehler als VC. In Ihrem Beispiel ist foo ein abhängiger Name, da er von 'T' abhängt. Wenn Sie dem Compiler nicht mitteilen, woher er kommt, kann er die Gültigkeit der Vorlage überhaupt nicht prüfen, bevor Sie sie instanziieren. Deshalb müssen Sie dem Compiler sagen, woher er kommt.