可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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)
回答1:
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.
};
回答2:
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;
回答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;
回答4:
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.
回答5:
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.
回答6:
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.