Fabio
Fabio

Reputation: 2287

Why when a template class inherits from another template class, typedefs need to be respecified and functions calls qualified?

When a template class inherits from another template class, typedefs in the base class must be redefined again (i.e. they are not automatically inherited), and function calls in the base class need to be qualified. Why is that? Isn't this already unambiguous?

So if I have 20 template classes, all defining the same typedefs, I am not able to introduce a base class containing these definitions them and inherit from it, as I have to redefine the typedefs anyway in every class, which defeats the purpose. This is makes the source code so unnecessarily verbose.

I can see this has been discussed in this question, but I do not understand the comment

The C++ name lookup rules specify that a name is only searched in a templated base classes if it depends on a template parameter (if it is a "dependent name"). If a name does not depend on a template parameter it isn't searched there.

What is the reason for this? It makes no sense to me.

Perhaps, the following code snippet would illustrate my question better:

#include <iostream>

template <unsigned N, typename T>
struct A
{
   typedef T U;
   static void foo(T x) { std::cout << N + x << "\n"; }
};

template <unsigned N, typename T>
struct B : public A<N, T>
{
    // Why do I have to redeclare U? Isn't is unambiguous already?
    typedef typename A<N, T>::U U;

    //  why do I have to specify B::? Isn't it unambiguous already?
    static void zoo(U x) { B::foo(x); }  
};

int main()
{
    B<2,int>().zoo(3);
    B<2,double>().zoo(3.5);
    return 0;
}

Upvotes: 11

Views: 564

Answers (2)

Davis Herring
Davis Herring

Reputation: 40013

The fundamental reason is that classes can be specialized:

template<class T>
struct A {};

template<class T>
struct B : A<T> {
  typedef typename A<T>::referent target;
  void niceName() {A<T>::uglyName();}
};

template<class T>
struct A<T*> {
  typedef T referent;
  void uglyName();
};

B<int*>::target i;  // OK: int

Of course, the reverse case (where the primary template defines useful things, and we simply fear that a specialization might change or remove them) is more common, which makes the rule seem arbitrary.

Upvotes: 1

Yola
Yola

Reputation: 19031

That's happening because of two phase lookup. Quote from here:

  1. Template definition time: when the template is initially parsed, long before it is instantiated, the compiler parses the template and looks up any "non-dependent" names. A name is "non-dependent" if the results of name lookup do not depend on any template parameters, and therefore will be the same from one template instantiation to another.
  2. Template instantiation time: when the template is instantiated, the compiler looks up any "dependent" names, now that it has the full set of template arguments to perform lookup. The results of this lookup can (and often do!) vary from one template instantiation to another.

So, during initial parsing compiler thinks that U is non-dependent name and tries to look it up and it can't find anything because it isn't allowed to look into the dependent base class. But if U were dependent name then compiler would look it up during instantiation phase and would find it in the base class.

BTW: VS will readily compile this, but they have added possibility of two phase lookup lately.

Upvotes: 3

Related Questions