Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visibility of members of base template class not directly inherited

The access to members of a template base class requires the syntax this->member or the using directive. Does this syntax extends also to base template classes which are not directly inherited?

Consider the following code:

template <bool X>
struct A {
  int x;
};

template <bool X>
struct B : public A<X> {
  using A<X>::x; // OK even if this is commented out
};

template <bool X>
struct C : public B<X> {
  // using B<X>::x; // OK
  using A<X>::x; // Why OK?
  C() { x = 1; }
};

int main()
{
  C<true> a;

  return 0;
}

Since the declaration of the template class B contains using A<X>::x, naturally the derived template class C can access to x with a using B<X>::x. Nevertheless, on g++ 8.2.1 and clang++ 6.0.1 the above code compiles fine, where x is accessed in C with a using that picks up x directly from A

I would have expected that C can not access directly to A. Also, commenting out the using A<X>::x in B still makes the code to compile. Even the combination of commenting out using A<X>::x in B and at the same time employ in C using B<X>::x instead of using A<X>::x gives a code that compiles.

Is the code legal?

Addition

To be more clear: the question arises on template classes and it is about the visibility of members inherited by template classes. By standard public inheritance, the public members of A are accessible to C, so using the syntax this->x in C one does indeed get access to A<X>::x. But what about the using directive? How does the compiler correctly resolve the using A<X>::x if A<X> is not a direct base of C?

like image 694
francesco Avatar asked May 22 '19 10:05

francesco


2 Answers

You are using A<X> where a base class is expected.

[namespace.udecl]

3 In a using-declaration used as a member-declaration, each using-declarator's nested-name-specifier shall name a base class of the class being defined.

Since this appears where a class type is expected, it is known and assumed to be a type. And it is a type that is dependent on the template arguments, so it's not looked up immediately.

[temp.res]

9 When looking for the declaration of a name used in a template definition, the usual lookup rules ([basic.lookup.unqual], [basic.lookup.argdep]) are used for non-dependent names. The lookup of names dependent on the template parameters is postponed until the actual template argument is known ([temp.dep]).

So it's allowed on account of the compiler not being able to know any better. It will check the using declaration when the class is instantiated. Indeed, one can put any dependent type there:

template<bool> struct D{};

template <bool X>
struct C : public B<X> {
  using D<X>::x; 
  C() { x = 1; }
}; 

This will not be checked until the value of X is known. Because B<X> can bring with it all sorts of surprises if it's specialized. One could for instance do this:

template<>
struct D<true> { char x; };

template<>
struct B<true> : D<true> {};

Making the above declaration be correct.

like image 113
StoryTeller - Unslander Monica Avatar answered Nov 06 '22 15:11

StoryTeller - Unslander Monica


Is the code legal?

Yes. This is what public inheritance does.

Is it possible to allow a template class derived from B to access to x only via this->x, using B::x or B::x? ...

You can use private inheritance (i.e. struct B : private A<X>), and arrange access to A<X>::x only through B's public/protected interface.

Also, if you're worried about having hidden members, you should use class instead of struct and specify the desired visibility explicitly.


Regarding the addition, note that:

(1) the compiler knows what object A<X>::x refers to given some instance of A<X> (because A is defined in the global scope, and X is the template parameter of C).

(2) You do indeed have an instance of A<X> - this is a ponter to a derived class (it doesn't matter if A<X> is a direct base class or not).

(3) The object A<X>::x is visible in the current scope (because the inheritances and the object itself are public).

The using statement is merely syntactic sugar. Once all types are resolved, the compiler replaces following use of x with the appropriate memory address in the instance, not unlike writing this->x directly.

like image 2
Benny K Avatar answered Nov 06 '22 14:11

Benny K