Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I call base template class method from derived class

I decided to test one of the examples in "Effective C++" and I'm not getting the result I expected. So, apparently this (simplified) code shouldn't compile:

template <class T>
struct A {
    void f(){}
};

template <class T>
struct B : public A <T> {
    void f2() { f(); }   // calling base function - will not compile
};

Here's the explanation (class names changed for simplicity) :

The code above won't compile, at least not with conformant compilers. Such compilers will complain that f doesn't exist. We can see that f is in the base class, but compilers won't look for it there.

We need to understand why. The problem is that when compilers encounter the definition for the class template B, they don't know what class it inherits from. Sure, it's A<T>, but T is a template parameter, one that won't be known until later (when B is instantiated). Without knowing what T is, there's no way to know what the class A<T> looks like. In particular, there's no way to know if it has a f function.

My compiler (Visual Studio) doesn't mind... It doesn't even show any warnings.

Is the above code correct or not?

like image 342
Oleksiy Avatar asked Oct 02 '13 01:10

Oleksiy


1 Answers

template <class T>
struct A {
    void f(){}
};

template <class T>
struct B : public A <T> {
    void f2() { f(); }   // calling base function - will not compile
};

In the derived template, the expression f() is not dependent on any template argument, so the compiler attempts to resolve it during the first phase lookup. At this point, the template has not yet been instantiated with the type, and the compiler won't look into the base A<T>. The reason is that the compiler could not possibly know whether for the type of the instantiation there is a specialization of A<T> that might not contain any members.

The solution is to make the expression dependent, the simplest way would be to qualify with this->:

template <typename T>
void B<T>::f2() {  this->f(); }

As the expression is now dependent, lookup is delayed until the second phase, where the type is substituted and A<T> is a concrete type. Another alternative is qualifying with the class where it is defined:

template <typename T>
void B<T>::f2() { A<T>::f(); }

Again the expression becomes dependent and will be resolved during the second phase. The main difference is that in this second case, the call is qualified and thus it does not use dynamic dispatch. If A<T>::f() was virtual it would still execute A<T>::f(), and not the final overrider.


Is the code correct? No. Does VS accept it? Yes.

This is a known non-conformance in the Visual Studio compiler, that does not implement two phase lookup. It delays all lookup inside the template to the second phase and at that point lookup succeeds.

like image 146
David Rodríguez - dribeas Avatar answered Nov 04 '22 02:11

David Rodríguez - dribeas