If the classes below were not templates I could simply have x in the derived class. However, with the code below, I have to use this->x. Why?
template <typename T>
class base {
protected:
int x;
};
template <typename T>
class derived : public base<T> {
public:
int f() { return this->x; }
};
int main() {
derived<int> d;
d.f();
return 0;
}
Short answer: in order to make
xa dependent name, so that lookup is deferred until the template parameter is known.Long answer: when a compiler sees a template, it is supposed to perform certain checks immediately, without seeing the template parameter. Others are deferred until the parameter is known. It’s called two-phase compilation, and MSVC doesn’t do it but it’s required by the standard and implemented by the other major compilers. If you like, the compiler must compile the template as soon as it sees it (to some kind of internal parse tree representation), and defer compiling the instantiation until later.
The checks that are performed on the template itself, rather than on particular instantiations of it, require that the compiler be able to resolve the grammar of the code in the template.
In C++ (and C), in order to resolve the grammar of code, you sometimes need to know whether something is a type or not. For example:
if A is a type, that declares a pointer (with no effect other than to shadow the global
x). If A is an object, that’s multiplication (and barring some operator overloading it’s illegal, assigning to an rvalue). If it is wrong, this error must be diagnosed in phase 1, it’s defined by the standard to be an error in the template, not in some particular instantiation of it. Even if the template is never instantiated, if A is anintthen the above code is ill-formed and must be diagnosed, just as it would be iffoowasn’t a template at all, but a plain function.Now, the standard says that names which aren’t dependent on template parameters must be resolvable in phase 1.
Ahere is not a dependent name, it refers to the same thing regardless of typeT. So it needs to be defined before the template is defined in order to be found and checked in phase 1.T::Awould be a name that depends on T. We can’t possibly know in phase 1 whether that’s a type or not. The type which will eventually be used asTin an instantiation quite likely isn’t even defined yet, and even if it was we don’t know which type(s) will be used as our template parameter. But we have to resolve the grammar in order to do our precious phase 1 checks for ill-formed templates. So the standard has a rule for dependent names – the compiler must assume that they’re non-types, unless qualified withtypenameto specify that they are types, or used in certain unambiguous contexts. For example intemplate <typename T> struct Foo : T::A {};,T::Ais used as a base class and hence is unambiguously a type. IfFoois instantiated with some type that has a data memberAinstead of a nested type A, that’s an error in the code doing the instantiation (phase 2), not an error in the template (phase 1).But what about a class template with a dependent base class?
Is A a dependent name or not? With base classes, any name could appear in the base class. So we could say that A is a dependent name, and treat it as a non-type. This would have the undesirable effect that every name in Foo is dependent, and hence every type used in Foo (except built-in types) has to be qualified. Inside of Foo, you’d have to write:
because
std::stringwould be a dependent name, and hence assumed to be a non-type unless specified otherwise. Ouch!A second problem with allowing your preferred code (
return x;) is that even ifBaris defined beforeFoo, andxisn’t a member in that definition, someone could later define a specialization ofBarfor some typeBaz, such thatBar<Baz>does have a data memberx, and then instantiateFoo<Baz>. So in that instantiation, your template would return the data member instead of returning the globalx. Or conversely if the base template definition ofBarhadx, they could define a specialization without it, and your template would look for a globalxto return inFoo<Baz>. I think this was judged to be just as surprising and distressing as the problem you have, but it’s silently surprising, as opposed to throwing a surprising error.To avoid these problems, the standard in effect says that dependent base classes of class templates just aren’t considered for search unless explicitly requested. This stops everything from being dependent just because it could be found in a dependent base. It also has the undesirable effect that you’re seeing – you have to qualify stuff from the base class or it’s not found. There are three common ways to make
Adependent:using Bar<T>::A;in the class –Anow refers to something inBar<T>, hence dependent.Bar<T>::A *x = 0;at point of use – Again,Ais definitely inBar<T>. This is multiplication sincetypenamewasn’t used, so possibly a bad example, but we’ll have to wait until instantiation to find out whetheroperator*(Bar<T>::A, x)returns an rvalue. Who knows, maybe it does…this->A;at point of use –Ais a member, so if it’s not inFoo, it must be in the base class, again the standard says this makes it dependent.Two-phase compilation is fiddly and difficult, and introduces some surprising requirements for extra verbiage in your code. But rather like democracy it’s probably the worst possible way of doing things, apart from all the others.
You could reasonably argue that in your example,
return x;doesn’t make sense ifxis a nested type in the base class, so the language should (a) say that it’s a dependent name and (2) treat it as a non-type, and your code would work withoutthis->. To an extent you’re the victim of collateral damage from the solution to a problem that doesn’t apply in your case, but there’s still the issue of your base class potentially introducing names under you that shadow globals, or not having names you thought they had, and a global being found instead.You could also possibly argue that the default should be the opposite for dependent names (assume type unless somehow specified to be an object), or that the default should be more context sensitive (in
std::string s = "";,std::stringcould be read as a type since nothing else makes grammatical sense, even thoughstd::string *s = 0;is ambiguous). Again, I don’t know quite how the rules were agreed. My guess is that the number of pages of text that would be required, mitigated against creating a lot of specific rules for which contexts take a type and which a non-type.