Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 383
user647486 Avatar asked Mar 04 '19 16:03

user647486


Video Answer


2 Answers

In that case i think you are making inheritance (using template). So Context::x refers to the x property of the parent. in that case A3, since A3 doesn't overwrite this property, you have the same as A1::x. In the second (gunc) you refer directly to the A1 using "this" thus no problem. In the third one (hunc, which is not used so) that the same is gunc with an implicite reference to self. (but i'm not entirely sure)

Also if you add in the A2 class :

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

The first one will print "45678"

If now you add in A3 while keeping A2

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

the first output will be 67890

like image 61
La pieuvre Avatar answered Sep 24 '22 13:09

La pieuvre


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.

like image 24
Handy999 Avatar answered Sep 26 '22 13:09

Handy999