Home > Back-end >  Different behavior when accessing a base class when it's a template
Different behavior when accessing a base class when it's a template

Time:04-15

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.

  • Related