Dimitar Asenov
Dimitar Asenov

Reputation: 26896

C++ cannot find type defined in template base class which inherits from the current template class

I'm trying to write a variation of the template class defining a super type idiom. The class Inherit introduces the type Super to denote the possibly very long super type, and also needs to know the derived type New to do some extra things which I'm not showing here.

This works fine if the type passed to New is not a template, but fails with templates. Here is a full example compiled with clang++-3.8 -std=c++1y -Wall (gcc gives the same output):

struct SomeBase {};

template<class New, class Base>
struct Inherit : Base {
    using Super = Inherit<New, Base>;
};

struct NonTemplate : Inherit<NonTemplate, SomeBase> {
    using X = Super;
    // compiles and is nice and short
};

template<class T>
struct Template : Inherit<Template<T>, SomeBase>
{
    using A = Super;
    // error: unknown type name 'Super'; did you mean 'NonTemplate::Super'?

    using B = typename Super;
    // error: expected a qualified name after 'typename'

    using C = Inherit::Super;
    // error: 'Inherit' is not a class, namespace, or enumeration

    using D = typename Inherit<Template<T>, SomeBase>::Super;
    // compiles, but what's the point?
};

int main() {
    return 0;
}

If I didn't need the New parameter I am also able to use Inherit with templates, but I really need New. I could make the code compile by using option D but that defeats the whole purpose.

I have two questions:

  1. What exactly is the language issue which prevents the type name Super from being known inside the class Template and why is that different in the NonTemplate case?
  2. Does anyone have an idea for a good solution to this problem?

Upvotes: 2

Views: 1429

Answers (1)

TartanLlama
TartanLlama

Reputation: 65700

The issue is that Inherit<Template<T>, SomeBase> is a dependent base class, i.e. the class depends on a template parameter. Names in dependent base class are hidden from unqualified lookup, so you need to qualify the name.

typename is needed because Inherit<Template<T>, SomeBase>::Super is a dependent name and the compiler needs you to tell it that it names a type, as it can only be certain at instantiation time.

See Where and why do I have to put the “template” and “typename” keywords? for an in-depth explanation of the typename keyword.


As for solutions, you could factor Super out into a type trait:

template<class New, class Base>
struct Inherit : Base {
};

namespace detail {
    template <class New, class Base>
    Inherit<New,Base> Super (const Inherit<New, Base>&);
}
template <class T>
using Super = decltype(detail::Super(std::declval<T>()));

This makes usage much simpler:

template<class T>
struct Template : Inherit<Template<T>, SomeBase>
{
    using D = Super<Template>;
};

If you find yourself needing this for multiple base classes, you could generalise it:

namespace detail {
    template <template <typename...> class T, class... Args>
    T<Args...> Super (const T<Args...>&);
}
template <template <typename...> class Base, class Derived>
using Super = decltype(detail::Super<Base>(std::declval<Derived>()));

template <typename T>
using InheritSuper = Super<Inherit,T>;

template<class T>
struct Template : Inherit<Template<T>, SomeBase>
{
    using D = InheritSuper<Template>;
};

Upvotes: 6

Related Questions