10

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)

Smeeheey
  • 9,906
  • 23
  • 39
  • 1
    "Base always defines Type irrespective of T." - there could be a specialization of Base that doesn't define it – M.M May 04 '17 at 11:29

6 Answers6

6

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.
};
Jonas
  • 6,915
  • 8
  • 35
  • 53
  • That's fair enough, and sorry I should have said in my question that I'm aware that this solution would pull in the required type: however, it is not entirely satisfactory. I'll edit my question to explain why. – Smeeheey May 04 '17 at 11:20
  • @Jonas I'd definitely make an exception for not requiring `typename` in a `using` statement, see http://stackoverflow.com/q/41841516/3093378 – vsoftco May 04 '17 at 11:48
  • @vsoftco Yes, it does seem clumsy. Luckily if one does forget it, most compilers are nice enough to suggest it. – Jonas May 04 '17 at 11:55
  • 2
    @Jonas - spot on. Exactly what I needed, solves my real problem too. Did not know you can refer to your own type like that, and thus be removed from non-deduced context. Elegant, much simpler than expected. Bravo – Smeeheey May 04 '17 at 12:00
  • Regarding your last example, it seems that the template argument `` is not required, and we can simply write `using Type = typename Derived::Type;`. Am I missing something? I remember using this pattern before and not having to specify the template arguments. – underscore_d May 11 '17 at 08:16
  • @underscore_d You are of course right, I suppose its a left over from intermediate.... I've updated the question – Jonas May 11 '17 at 08:24
4

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;
vsoftco
  • 55,410
  • 12
  • 139
  • 252
3

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.

Yes.


In this case, this is frustrating, as Base always defines Type irrespective of T.

Yes.

But in general it would be entirely infeasible to detect whether this were true, and extremely confusing if the semantics of the language changed when it were true.


Perhaps there is a good reason for this.

C++ is a general-purpose programming language, not an optimised-for-the-program-Smeeheey-is-working-on-today programming language. :)


What is the rule in the standard that states this must happen?

It's this:

[C++14: 14.6.2/3]: In the definition of a class or class template, if a base class depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member. [..]


What is a good workaround for precisely this type of problem?

You already know — qualification:

using Alias = typename Base<T>::Type;
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • It would be entirely infeasible - yes, agreed. However, why can the compiler simply not parse `Derived` _at all_ outside of an instantiation context? This would solve the problem: I guess there must be a good reason for not doing this, I'm just wondering what this good reason is. – Smeeheey May 04 '17 at 11:29
  • @Smeeheey: I don't understand your suggestion, sorry. – Lightness Races in Orbit May 04 '17 at 11:29
  • @Smeeheey It is just too complicated, considering that you can have multiple translation units and there is no specified order when compiling them. – vsoftco May 04 '17 at 11:30
  • @BoundaryImposition: I'm saying why does the compiler have to parse `Derived` at all outside of an instantiation context? If it got a `Derived`, parse that. If it didn't get any `Derived` uses at all, why parse it? After all, code is only generated for specific instantiations, why even bother looking at it outside of one? – Smeeheey May 04 '17 at 11:34
  • @Smeeheey: It doesn't. – Lightness Races in Orbit May 04 '17 at 11:35
  • The compiler error regarding `Type` not existing to can only happen outside of an instantiation context. Any actual instantiation context will find it perfectly well defined (as per the declaration of `b`) – Smeeheey May 04 '17 at 11:37
  • @Smeeheey: No, it won't, because unqualified lookup doesn't look in bases depending on a template parameter. Nobody's claiming that it's impossible for some different language to support your specific code in this specific case, but C++ is not some different language specifically designed around the needs of your program. You're not seeing the bigger picture. And frankly I don't understand why you find the obvious solution to be unsatisfactory. – Lightness Races in Orbit May 04 '17 at 11:38
  • I think you're misunderstanding me. I'm not insisting the compiler 'know' that there is a `Type` for every possible instantiation. I'm simply saying don't check at all until you hit an actual instantiation. If I use an instantiation that happens to not define `Type` as required (because of some specialisation of `Base` or whatever) - by all means give me a compiler error then. – Smeeheey May 04 '17 at 11:42
  • 1
    @Smeeheey If you read [C++ Templates: the complete guide](https://www.amazon.ca/Templates-Complete-Guide-David-Vandevoorde/dp/0201734842) by **the** experts in the field, you'll see why. It's too long to detail here, but it mainly has to do with how complicated the whole thing can become. In fact, there was a previous proposal for [`export template`](http://stackoverflow.com/q/5416872/3093378) which would have allowed to put the definition of templates in a separate `.cpp` file, which miserably failed for the same reason. – vsoftco May 04 '17 at 11:43
  • @Smeeheey: It _doesn't_ "check at all until you hit an actual instantiation." You are describing how template instantiation works, but that still doesn't change anything related to this question! It is simply not sufficient to allow for this unqualified base lookup _in the general case_ without making the language rules far, far, far too complicated. Much like, apparently, your design is ;) – Lightness Races in Orbit May 04 '17 at 11:45
  • Again you missed the point. I'm not trying to achieve unqualified base lookup, but rather prevent the parsing of `Alias` in a non-deduced context in the first place. Jonas's answer achieves precisely that. – Smeeheey May 04 '17 at 12:10
  • Er Jonas's answer says exactly what mine does, minus addressing all the rest of the question. But if that's what you're happy with, then fine. – Lightness Races in Orbit May 04 '17 at 12:20
  • @smee the compiler is allowed not to parse the class template body outside of instantiation. See my "dependent names" answer elsewhere. However parsing it immediately gives the programmer error checking earlier and therefore can improve correctness – Johannes Schaub - litb May 11 '17 at 09:03
  • It is comparable with trying to typecheck dynamically typed programs at compile/bytecode-emission time. If because of dynamic controlflow the compiler has no clue about types, you would need to gives it hints – Johannes Schaub - litb May 11 '17 at 09:05
  • I agree, it is useful. However, at times (such as this one) you want the ability to forcefully inhibit it. By making a type-dependant alias, the accepted answer achieves that in this case. BTW I don't know which answer you're referring to as "answer elsewhere" – Smeeheey May 11 '17 at 09:08
  • @Smeeheey: Click on his name and follow the link from his profile. – Lightness Races in Orbit May 11 '17 at 09:19
2

When defining a template, sometimes things need to be a bit more explicit:

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

Rougly speaking: a template is a blueprint of sorts. Nothing actually exists until the template get instantiated.

When doing the same thing, but in a non-template context, a C++ compiler will try to figure out what Type is. It'll try to find it in the base classes, and go from there. Because everything is already declared, and things are pretty much cut-and-dry.

Here, a base class does not really exist until the template gets instantiated. If you already know about template specialization, that you should realize that the base class may not actually turn out to have a Type member, when the template gets instantiated if there's a specialization for the base class defined later down the road, that's going to override the whole thing, and turn it inside and out.

As such, when encountering just a plain, old, Type in this context, the compiler can't make a lot of assumptions. It can't assume that it can look in any defined template base classes because those base classes may not actually look anything like the compiler thinks they will look, when things start to solidify; so you have spell everything out, explicitly, for the compiler, and tell the compiler exactly what your are trying to do, here.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • So why not just delay the parsing until you have an instantiation? Why does it need to parse `Derived` in a non-deduced context at all? – Smeeheey May 04 '17 at 11:33
  • You have no choice but to parse it. The compiler parses the code from start to finish. The forward march of progress cannot be stopped. – Sam Varshavchik May 04 '17 at 12:06
  • Not true. See Jonas's answer for how to prevent the compiler from parsing a typedef in a non-deduced context – Smeeheey May 04 '17 at 12:08
  • I don't see any difference. The germane portion of his answer is the same as mine's. – Sam Varshavchik May 04 '17 at 12:11
  • There is a critical difference: it achieves the use of `Type` directly from the type hierarchy as required in my comments (i.e. insists that `Type` is actually a member `Type` of itself - your answer and others like it have no such requirement, and would work even if `Derived` did not in fact derive from `Base`) – Smeeheey May 04 '17 at 12:15
2

You cannot use your base class types in a non-deduced context. C++ refuses to assume unbound names refer to things in your base class.

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

using Type=std::string;

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

Now let us look at what is going on here. Type is visible in Derived -- a global one. Should Derived use that or not?

Under the rule of "parse nothing until instantiated", we use the global Type if and only if T is int, due to the Base specialization that removes Type from it.

Following that rule we run into the problem that we can diagnose basically no errors in the template prior to it being instantiated, because the base class could replace the meaning of almost anything! Call abs? Could be a member of the parent! Mention a type? Could be from the parent!

That would force templates to basically be macros; no meaningful parsing could be done prior to instantiating them. Minor typos could result in massively different behavior. Almost any incorrectness in a template would be impossible to diagnose without creating test instances.

Having templates that can be checked for correctness means that when you want to use your parent class types and members, you have to say you are doing so.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

As a partial response about the point

[T]his is frustrating, as Base always defines Type irrespective of T.

I'd say: no it does not.

Please consider the following example differing from yours only by the one line definition of Base<void> and of the definition of Alias:

#include <iostream>

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

template <typename T>
struct Derived : Base<T>
{
    using Alias = typename Base<T>::Type; // error: no type named 'Type' in 'struct Base<void>'
};

template<> struct Base<void> {};

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

    return 0;
}

In the context of template <typename T> struct Derived : Base<T>, there is no guaranty that Type exists. You must explicitly tell your compiler than Base<T>::Type is a type (with typename) and if you ever fail this contract, you'll end up with a compilation error.

YSC
  • 38,212
  • 9
  • 96
  • 149
  • That's true, but misses the point somewhat. If there is an instantiation where `Type` really is invalid, break then. However, the code as originally posted doesn't compile even if there is no such 'bad' instantiation. Doing `using Alias = typename Base::Type` solves that problem but is not really what I'm looking for as described in my comments above – Smeeheey May 04 '17 at 11:54
  • At the time the compiler parses `Derived`, it does not know that. Don't forget this is a trivial case where everything is in the same file, but in real life the generic behavior for `Base` and its specialization may be in different header files included in an unspecified order. Templates are merely recipes to define classes: `Derived` is only defined when it is used in `main()`. – YSC May 04 '17 at 12:00
  • 'At the time the compiler parses that, it does not know that' - precisely, why does it even need to parse it at that time, rather than later when there is an instantiation context? See Jonas's answer for how to achieve exactly that. – Smeeheey May 04 '17 at 12:02