Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is using base class definitions in non-deduced context not permitted, and how to get around this?

I have the following code:

#include <iostream>

template <typename T>
struct Base
{
    using Type = int;
};

template <typename T>
struct Derived : Base<T>
{
    //uncommmenting the below cause compiler error
    //using Alias = Type;
};

int main()
{
    Derived<void>::Type b = 1;
    std::cout << b << std::endl;

    return 0;
}

Now the typename Type is available to Derived if its in a deduced context - as shown by the perfectly valid declaration of b. However, if I try to refer to Type inside the declaration of Derived itself, then I get a compiler error telling me that Type does not name a type (for example if the definition of Alias is uncommented).

I guess this is something to do with the compiler not being able to check whether or not Type can be pulled in from the base class when it is parsing the definition of Derived outside the context of a specific instantiation of parameter T. In this case, this is frustrating, as Base always defines Type irrespective of T. So my question is twofold:

1). Why on Earth does this happen? By this I mean why does the compiler bother parsing Derived at all outside of an instantiation context (I guess non-deduced context), when not doing so would avoid these 'bogus' compiler errors? Perhaps there is a good reason for this. What is the rule in the standard that states this must happen?

2). What is a good workaround for precisely this type of problem? I have a real-life case where I need to use base class types in the definition of a derived class, but am prevented from doing so by this problem. I guess I'm looking for some kind of 'hide behind non-deduced context' solution, where I prevent this compiler 'first-pass' by putting required definitions/typedefs behind templated classes or something along those lines.

EDIT: As some answers below point out, I can use using Alias = typename Base<T>::Type. I should have said from the outset, I'm aware this works. However, its not entirely satisfactory for two reasons: 1) It doesn't use the inheritance hierarchy at all (Derived would not have to be derived from Base for this to work), and I'm precisely trying to use types defined in my base class hierarchy and 2) The real-life case actually has several layers of inheritance. If I wanted to pull in something from several layers up this becomes really quite ugly (I either need to refer to a non-direct ancestor, or else repeat the using at every layer until I reach the one I need it at)

like image 442
Smeeheey Avatar asked May 04 '17 11:05

Smeeheey


2 Answers

Because type is in a "dependent scope" you can access it like this:

typename Base<T>::Type

Your Alias should then be defined like this:

using Alias = typename Base<T>::Type;

Note that the compiler doesn't, at this point, know if Base<T>::type describes a member variable or a nested type, that is why the keyword typename is required.

Layers

You do not need to repeat the definition at every layer, here is an example, link:

template <typename T>
struct intermediate : Base<T>
{
    // using Type = typename Base<T>::Type; // Not needed
};

template <typename T>
struct Derived : intermediate<T>
{
    using Type = typename intermediate<T>::Type;
};

Update

You could also use the class it self, this relies on using an unknown specializations.

template <typename T>
struct Derived : Base<T>
{
    using Type = typename Derived::Type; // <T> not required here.
};
like image 144
Jonas Avatar answered Oct 28 '22 03:10

Jonas


The problem is that Base<T> is a dependent base class, and there may be specializations for it in which Type is not anymore defined. Say for example you have a specialization like

template<>
class Base<int>
{}; // there's no more Type here

The compiler cannot know this in advance (technically it cannot know until the instantiation of the template), especially if the specialization is defined in a different translation unit. So, the language designers chose to take the easy route: whenever you refer to something that's dependent, you need to explicitly specifify this, like in your case

using Alias = typename Base<T>::Type;
like image 42
vsoftco Avatar answered Oct 28 '22 02:10

vsoftco