Visibility of members of base template class not directly inherited

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.


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.