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.