44
votes

Why can't a C++ compiler recognize that g() and b are inherited members of Superclass as seen in this code:

template<typename T> struct Superclass {
 protected:
  int b;
  void g() {}
};

template<typename T> struct Subclass : public Superclass<T> {
  void f() {
    g(); // compiler error: uncategorized
    b = 3; // compiler error: unrecognized
  }
};

If I simplify Subclass and just inherit from Subclass<int> then it compiles. It also compiles when fully qualifying g() as Superclass<T>::g() and Superclass<T>::b. I'm using LLVM GCC 4.2.

Note: If I make g() and b public in the superclass it still fails with same error.

2
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 prefixes Superclass<T>::b and Superclass<T>::g(), 2) Use the prefixes this->a and this->g(), 3) Add statements using Superclass<T>::a and using Superclass<T>::g, 4) Use a global compiler switch that enables the permissive mode. The pros & cons of these solutions are described in stackoverflow.com/questions/50321788/…George Robinson

2 Answers

67
votes

This can be amended by pulling the names into the current scope using using:

template<typename T> struct Subclass : public Superclass<T> {
  using Superclass<T>::b;
  using Superclass<T>::g;

  void f() {
    g();
    b = 3;
  }
};

Or by qualifying the name via the this pointer access:

template<typename T> struct Subclass : public Superclass<T> {
  void f() {
    this->g();
    this->b = 3;
  }
};

Or, as you’ve already noticed, by qualifying the full name.

The reason why this is necessary is that C++ doesn’t consider superclass templates for name resolution (because then they are dependent names and dependent names are not considered). It works when you use Superclass<int> because that’s not a template (it’s an instantiation of a template) and thus its nested names are not dependent names.

18
votes

Konrad's answer doesn't ask or answer the final "why" in all of this. It's not just the C++ committee arbitrarily saying "hey, give up on dependent names, nobody likes them anyway". Rather, the compiler does some checking of the templates even before they're instantiated, and it can't make any sense of g() or b until it knows T, as it can't - in general - select between possible specialisations of the base class (e.g. SuperClass<X> may have int b while SuperClass<Y> has void b() and SuperClass<Z> doesn't have b at all). The more explicit forms are just saying "trust me - this must come from the base class at instantiation time (otherwise there'll be a compiler error then)".