'Why are unqualified names from nondependent base classes favored over template parameters
The C++ standard says that unqualified names from nondependent base classes are preferred over template parameters. What is the reasoning behind this?
The following snippet is from C++ Templates:
#include <iostream>
template <typename X>
class Base {
public:
int basefield;
using T = int;
};
class D1 : public Base<Base<void>> {
public:
void f() { basefield = 3; // works as normal
}
};
template <typename T>
class D2 : public Base<double> {
public:
void f() { basefield = 7; }
T strange; // what type am I ???
};
int main() {
D2<std::string> d;
d.strange = 100;
}
Solution 1:[1]
Unqualified lookup is approximately equivalent to trying a qualified lookup in each containing scope and keeping the first hit. Since D2<…>::T
can find Base<double>::T
, it is the lookup in the class’s scope that succeeds; that lookup naturally precedes that for the template parameters that are introduced lexically outside the class.
This preference also avoids having an unqualified name change meaning between
template<class T>
struct A : B {
Z z;
void f();
// …
};
and
template<class Z>
void A<Z>::f() {
Z z;
// …
}
although the same change is still possible for namespace-scope names.
That said, even the committee has been divided on this particular rule because it is weird to prefer a name you can’t see in a base class over one you can see in the template-head.
Solution 2:[2]
// what type am I ???
When an unqualified name is looked up in the templated derivation(like D2
), the nondependent bases are considered before the list of template parameters.
In your case, this means that, the member strange
of the class template D2
always has the type T
corresponding to Base<double>::T
(which is nothing but, int
).
You can confirm this by changing d.strange = 100;
to:
d.strange = "100"; //this will give error saying: invalid conversion from ‘const char*’ to ‘Base::T’ {aka ‘int’}
The above statement produces the error saying:
invalid conversion from ‘const char*’ to ‘Base::T’
{aka ‘int’}
which confirms that strange
has type int
.
What is the reasoning behind this?
One reason for why the standard says so might be because as the lookup starts for a name, it goes upwards(in the up direction) from where the name was encountered. Now, while searching for the name in the upward direction, the non-dependent Base is encountered first and so the name is resolved belonging to the non-dependent Base(obviously the Base must have a member which is named the same that is being looked up). That is, since in this case the Base is independent of the template parameter and since it comes first while the lookup happens(from the point of the declaration of the name to the upwards direction), it makes sense that the non-dependent Base takes precedence.
On the other hand in case of a dependent Base, the template parameter must first be known in order for the dependent Base to be known. That is, in this case the Base is not independent of the template parameter. And so when the lookup happens(again in the upwards direction), even though the dependent Base is encountered before the template parameter, the dependent Base is not known until the template parameter is known. So in this case, it makes sense for the template parameter to take precedence.
This is my current reasoning of why the standard says so.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |