c++templatesscope-resolution

Scope resolution in templated inheritance (possibly what is called mixin)


Suppose I have the templated classes

#include <iostream>

class A1 {
public:
  int x{314159};
};

template<typename Context>
class A2 : public Context {};

template<typename Context>
class A3 : public Context {};

template<typename Context>
class A4 : public Context {
public:
  int func() {
    return Context::A1::x;
  }

  int gunc() {
    return this->A1::x;
  }

  int hunc() {
    return A1::x;
  }
};

int main() {
  A4<A3<A2<A1>>> my_A;

  std::cout << "x = func() = " << my_A.func() << std::endl;
  std::cout << "x = gunc() = " << my_A.gunc() << std::endl;
  std::cout << "x = hunc() = " << my_A.hunc() << std::endl;

  return 0;
}

Inside the definition of the templated class A4, at least when only the instance type A4<A3<A2<A1>>> is used, it seems to be possible to refer to x as either

this->A1::x;

or

Context::A1::x;

or

A1::x;

Question 1: Are these equivalent? Well, I think I can see them not being equivalent from the point of view of the templated class A4 viewed in isolation. For Context::A1::x to work its template parameter should contain an x. For this->A1::x to work it should contain a scope called A1, which in turn should contain an x. And for A1::x to work the scope of A4 itself should contain a scope called A1 containing an x. My intention is to ask if they are equivalent from the point of view of the type A4<A3<A2<A1>>>.

Nota bene: gcc 8.2 with -O03 -std=c++17 produces the same assembly code in each case. Namely, I compiled the code with only one of the functions func, gunc, and hunc and only one call to the corresponding one, and this compiler produced identical executables. Of course, strictly speaking this doesn't necessarily imply that for the language in abstract those expressions are equivalent.

Question 2: How does the 'unpacking' of the scope of x works in each case? Maybe this question doesn't make sense or is not exactly what I want to ask. Specially if the answer to Question 1 is that they are equivalent. Allow me modify this question after I find more information about Question 1, or ignore this question at first.

Note to Question 2: This observation might clarify why I am unsure how the unpacking works. If in the templated class A4 we had one more method

int iunc() {
  return Context::Context::A1::x;
}

then the compilation fails with

memberTemplatedParent.cpp: In instantiation of ‘int A4<Context>::iunc() [with Context = A3<A2<A1> >]’:
memberTemplatedParent.cpp:48:45:   required from here
memberTemplatedParent.cpp:37:22: error: no type named ‘Context’ in ‘class A3<A2<A1> >’
 return Context::Context::A1::x;
                  ^

So, at least for gcc at the moment that the type instance of A4 is being created, the template parameter of its template parameter is not a valid name (or I didn't name it properly in Context::Context::A1::x).


Solution

  • Questions 1 and 2:

    All versions are equivalent for the instantiation you have chosen. As long as it is not ambiguous, you can use the member x directly without specifying the scope. If the member is not in the current class, the base class is checked, and so further.

    If you specify a particular base class and the member x is not there, again the base class is consulted.

    For your particular specialization, you have

    class A2<A1> : public A1 {};
    
    class A3<A2<A1>> : public A2<A1>{};
    
    class A4<A3<A2<A1>>> : public A3<A2<A1>> {
    public:
      int func() {
        return A3<A2<A1>>::A1::x;  // fine: search for x in A1,
                                   // where A1 is searched in A3<A2<A1>>
      }
      int gunc() {
         return this->A1::x; // fine: why not specifying A1 directly. The this pointer
                             // is not required here but adding does not cause any harm.
      }
      int hunc() {
         return A1::x; // fine: why not specifying A1 directly.
      }
      int iunc() {
         return x; // fine: would be possible as well
      }
    

    };

    Last question:

    int iunc() {
      return Context::Context::A1::x;
    }
    

    reads as follows after template instanciation

    int iunc() {
      return A3<A2<A1>>::Context::A1::x;
    }
    

    The compiler now complains that there is no typedef in the class A3<A2<A1>> which introduces the name Context. The template parameter is only visible within the class template.