GCC issue: using a member of a base class that depends on a template argument
Asked Answered
G

5

37

The following code doesn't compile with gcc, but does with Visual Studio:

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

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

I get the error:

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

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

But it should be! If I change bar to

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

then it does compile, but I don't think I have to do this. Is there something in the official specs of C++ that GCC is following here, or is it just a quirk?

Githens answered 14/8, 2008 at 17:39 Comment(2)
See In a templated derived class, why do I need to qualify base class member names with “this->” inside a member function?Bimolecular
This happens because of the two-phase name lookup (which not all compilers use by default). There are 4 solutions to this problem: 1) Use the prefix A<T>::foo, 2) Use the prefix this->foo, 3) Add a statement using A<T>::foo, 4) Use a global compiler switch that enables the permissive mode. The pros & cons of these solutions are described in #50322288Italic
U
12

This changed in gcc-3.4. The C++ parser got much more strict in that release -- per the spec but still kinda annoying for people with legacy or multi-platform code bases.

Upandcoming answered 14/8, 2008 at 17:50 Comment(0)
B
34

David Joyner had the history, here is the reason.

The problem when compiling B<T> is that its base class A<T> is unknown from the compiler, being a template class, so no way for the compiler to know any members from the base class.

Earlier versions did some inference by actually parsing the base template class, but ISO C++ stated that this inference can lead to conflicts where there should not be.

The solution to reference a base class member in a template is to use this (like you did) or specifically name the base class:

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

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

More information in gcc manual.

Betteanne answered 14/8, 2008 at 18:9 Comment(2)
On the one hand, that kind of makes sense. But on the other hand, it feels really lame. The compiler doesn't need to know what foo refers to until the template is instantiated, at which point, it should be able to recognize the foo member in A. C++ has way too many of these weird corner cases.Gailgaile
yes, this is totally unacceptable... cant know before instantiate? then wait for instantiation as usual in templates.. that's the spirit of it, no? what a mess...Rosalvarosalyn
G
19

Wow. C++ never ceases to surprise me with its weirdness.

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

That's just all kinds of crazy. Thanks, David.

Here's the "temp.dep/3" section of the standard [ISO/IEC 14882:2003] that they are referring to:

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

Gailgaile answered 14/8, 2008 at 18:5 Comment(0)
U
12

This changed in gcc-3.4. The C++ parser got much more strict in that release -- per the spec but still kinda annoying for people with legacy or multi-platform code bases.

Upandcoming answered 14/8, 2008 at 17:50 Comment(0)
I
8

The main reason C++ cannot assume anything here is that the base template can be specialized for a type later. Continuing the original example:

template<>
class A<int> {};

B<int> x; 
x.bar();//this will fail because there is no member foo in A<int>
Ironist answered 14/8, 2008 at 21:6 Comment(0)
P
3

VC doesn't implemented two-phase lookup, while GCC does. So GCC parses templates before they are instantiated and thus finds more errors than VC. In your example, foo is a dependent name, since it depends on 'T'. Unless you tell the compiler where it comes from, it cannot check the validity of the template at all, before you instantiate it. That's why you have to tell the compiler where it comes from.

Pyretic answered 17/10, 2008 at 13:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.