When I write A::B::C, with A being a class, and B being its base class, I assume I'm accessing C which is defined in that base B. This wouldn't work when B isn't actually a base of A. However, the same apparently isn't true when B is a template, e.g. A::B<123>::C still gives me B<123>::C, and it doesn't seem to matter whether B<123> is actually a base of A or not. I'm at a loss why there could be a difference. Does it not interpret A::B<123> as accessing base class B<123> of class A? Why not? Can it be rewritten somehow so it does interpret it like accessing a base class?
Here's a snippet explaining what's done in detail, with comments explaining every step:
// Here we show that we can't access a non-existent base of A
namespace WorksAsExpectedWithoutTemplates {
struct B
{
using C = void;
};
struct D
{
using C = void;
};
struct A: B
{
};
// Compiles as expected, B is a base of A, so Foo is B::C, aka void
using Foo = A::B::C;
// Doesn't compile, as expected, because D isn't a base of A, even though D::C
// exists
using Bar = A::D::C; // WE DON'T EXPECT THIS TO COMPILE, WHICH IS FINE
}
// Now we try the same thing with templates, and it doesn't behave the same way.
// Why?
namespace ActsDifferentlyWithTemplates {
template< int >
struct B
{
using C = void;
};
struct A: B< 42 >
{
};
// Compiles as expected, B< 42 > is a base of A, so Foo is B< 42 >::C, aka void
using Foo = A::B< 42 >::C;
// Compiles, Bar is B< 123 >::C, even though B< 123 > is not a base of A. Why
// does this behave differently than in the non-template case above? Can this be
// rewritten differently so that this wouldn't compile, same as in
// WorksAsExpectedWithoutTemplates, since B< 123 > isn't a base of A?
using Bar = A::B< 123 >::C; // WHY DOES THIS COMPILE? B< 123 > isn't a base of A
}
CodePudding user response:
template< int > struct B has an injected-class-name B that acts as a template name if it immediately precedes a <. The class struct A inherits this.
So, A::B< 123 >::C is the same as B< 123 >::C, not the base class B< 42 >. For example:
template<int X>
struct B {
using C = char[X];
};
struct A : B<42> {};
using Foo = A::B<42>::C;
using Bar = A::B<123>::C;
using Baz = A::B::C; // (injected-class-name without template)
static_assert(sizeof(Foo) == 42);
static_assert(sizeof(Bar) == 123); // This is a different type
static_assert(sizeof(Baz) == 42);
None of these cases actually are "accessing a base class". They are all inheriting the injected-class-name from the base class like any other member type alias / member template.
