6

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;
}
Davis Herring
  • 36,443
  • 4
  • 48
  • 76
Nathan Doromal
  • 3,437
  • 2
  • 24
  • 25
  • 3
    I recommend adding the `language-lawyer` tag. – Eljay Apr 05 '22 at 15:11
  • 4
    What purpose does `D1` fill in this example? It looks like it could be removed. – Ted Lyngmo Apr 05 '22 at 15:21
  • 1
    It is because the language standard says that's the way it is [as seen in these quotes](https://stackoverflow.com/questions/28029722/typedef-and-template-parameter-with-same-name). _Why_ ... well, I'm sure they (the C++ committee) had ambiguity on the table as well as that the template parameter would hide the base class types but that they decided that that would cause bigger problems. – Ted Lyngmo Apr 05 '22 at 15:34
  • @TedLyngmo Pity they took it off the table, IMO. Doing things like this sounds like a recipe for trouble. – Paul Sanders Apr 05 '22 at 15:36
  • You are right about D1. I added an addl comment that perhaps shows the original intention from the snippet. – Nathan Doromal Apr 05 '22 at 15:40
  • @PaulSanders True. I haven't thought about this before. If people started adding the member class `struct T { ...operators etc... };` inside their inheritable classes, it'd certainly cause one or two hard to find problems. – Ted Lyngmo Apr 05 '22 at 15:40

2 Answers2

2

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.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • What about the language-lawyer tag? – Jason Apr 05 '22 at 15:51
  • @Anya: What about it? I don’t think that reading [temp.local]/7 will make the behavior seem any better motivated, but merely get you to the point of being able to ask the question. – Davis Herring Apr 05 '22 at 16:16
  • I guess this tag doesn't make sense there as it doesn't look like the OP needs to know what lawyers have to say about it but rather needs an answer from an engineer. @Anya – ixSci Apr 05 '22 at 16:31
  • @ixSci I also think that there is no need for the language-lawyer tag here. But in that case, the other [answer](https://stackoverflow.com/a/71754532/17881448) is more complete and understandable(atleast to me). – Jason Apr 05 '22 at 16:34
1

// 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.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • It seems OP knows this and asks _"What is the reasoning behind this?"_. – Ted Lyngmo Apr 05 '22 at 15:32
  • @TedLyngmo In OP's question it is written: *"// what type am I ???"* So OP's question clearly asking about the type of `strange` also. – Jason Apr 05 '22 at 15:33
  • 3
    I agree with Ted. I think you have read a little too much into that one-liner. – Paul Sanders Apr 05 '22 at 15:33
  • @AnoopRana That's true too ... I'm a bit confused about _what_ OP is asking really :-) – Ted Lyngmo Apr 05 '22 at 15:34
  • 1
    @TedLyngmo Isn't the title of the post clear enough? – Paul Sanders Apr 05 '22 at 15:35
  • @TedLyngmo Yeah me too when i read OP's question for the first time and saw that "what type am i". So i answered according to that. – Jason Apr 05 '22 at 15:35
  • @PaulSanders Ah, yes, I tend to forget the title after having read the text in the question :-) – Ted Lyngmo Apr 05 '22 at 15:36
  • I'm asking for the rationale behind it. I had been puzzling over the "why" behind this snippet all morning. – Nathan Doromal Apr 05 '22 at 15:38
  • @TedLyngmo My thinking for the why part is that: 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). On the other hand in case of dependent Base, the template parameter must first be known in order for the dependent Base to be known. I am thinking of adding this in my answer. – Jason Apr 05 '22 at 16:11
  • @TedLyngmo *..continued*: Although this is my speculation. I am thinking of adding this in my answer. Can you provide some input on this? – Jason Apr 05 '22 at 16:11
  • It sounds reasonable (just like David Herring's answer). If you put the relevant parts of the standard in the answer, it may answer OP:s question. – Ted Lyngmo Apr 05 '22 at 16:15