10

I decided to test one of the examples in "Effective C++" and I'm not getting the result I expected. So, apparently this (simplified) code shouldn't compile:

template <class T>
struct A {
    void f(){}
};

template <class T>
struct B : public A <T> {
    void f2() { f(); }   // calling base function - will not compile
};

Here's the explanation (class names changed for simplicity) :

The code above won't compile, at least not with conformant compilers. Such compilers will complain that f doesn't exist. We can see that f is in the base class, but compilers won't look for it there.

We need to understand why. The problem is that when compilers encounter the definition for the class template B, they don't know what class it inherits from. Sure, it's A<T>, but T is a template parameter, one that won't be known until later (when B is instantiated). Without knowing what T is, there's no way to know what the class A<T> looks like. In particular, there's no way to know if it has a f function.

My compiler (Visual Studio) doesn't mind... It doesn't even show any warnings.

Is the above code correct or not?

Oleksiy
  • 37,477
  • 22
  • 74
  • 122
  • f() needs to be fully qualified with either base class name or `this->` – goji Oct 02 '13 at 01:54
  • @Troy "f() needs to be fully qualified.." sorry what does that mean? – Oleksiy Oct 02 '13 at 01:55
  • 1
    @Oleksiy It works on MSVC because that compiler doesn't implement two-phase name lookup and thus doesn't try to resolve dependent names prior to the instantiation of a template. At instantiation time, `A::f` is no longer a dependent name since `T` is known. And *fully qualified* means it'll work if you write `A::f()`. The C++-faq explains the pitfalls of using that approach. – Praetorian Oct 02 '13 at 01:57
  • See: http://coliru.stacked-crooked.com/a/61a8ea59d90d7612 – goji Oct 02 '13 at 01:57
  • Regarding two-phase lookup on Visual C++, you might want to read [this question and its answers](http://stackoverflow.com/questions/6273176/what-exactly-is-broken-with-microsoft-visual-cs-two-phase-template-instanti) (SimonBuchan's answer is the most informative IMHO). – syam Oct 02 '13 at 01:59
  • You need to use "-fpermissive" in g++ in order to compile it. But, that's another matter from your question and not recommended. But when you compile with this flag, the "g++" will still warn you that "warning: there are no arguments to ‘f’ that depend on a template parameter, so a declaration of ‘f’ must be available [-fpermissive]". – noel aye Oct 02 '13 at 02:29
  • The important part of the quoted text is: "at least not with conformant compilers". Visual Studio (or "MSVC") does not fall under that category. – Mikael Persson Oct 02 '13 at 02:55
  • 2
    @Praetorian: *fully qualified* is an overstatement. The call has to be made dependent, which can happen by adding `this->` or by *qualifying*. There is no need to *fully* qualify the call, only the base type is needed. – David Rodríguez - dribeas Oct 02 '13 at 03:40
  • @david, no. Incorrect. You *must* qualify the name. It can be done by adding "this" or the base class name. You are saying that the mere fact that the name becomes dependent is sufficient. But that is not correct. Not using an unqualified name is the thing you must do (and using a dependent name is just necessary, not sufficient). For example, if f had a parameter, `f (T ())` would still not work. Both the dependent names `T` and `f` never find a member of a dependent base class *even during instantiation*. – Johannes Schaub - litb Oct 02 '13 at 06:30
  • @litb: My understanding is that for a name to be call *qualified* there must be some use of the `::` operator. I have not found (yet) anywhere in the standard where `this->f` is considered a qualified name (it is not a qualified name AFAIK). Thanks for pointing out the `f(T())` example, I learned something new about lookup today (I seem to learn something new about lookup every other day) – David Rodríguez - dribeas Oct 02 '13 at 13:23

1 Answers1

11
template <class T>
struct A {
    void f(){}
};

template <class T>
struct B : public A <T> {
    void f2() { f(); }   // calling base function - will not compile
};

In the derived template, the expression f() is not dependent on any template argument, so the compiler attempts to resolve it during the first phase lookup. At this point, the template has not yet been instantiated with the type, and the compiler won't look into the base A<T>. The reason is that the compiler could not possibly know whether for the type of the instantiation there is a specialization of A<T> that might not contain any members.

The solution is to make the expression dependent, the simplest way would be to qualify with this->:

template <typename T>
void B<T>::f2() {  this->f(); }

As the expression is now dependent, lookup is delayed until the second phase, where the type is substituted and A<T> is a concrete type. Another alternative is qualifying with the class where it is defined:

template <typename T>
void B<T>::f2() { A<T>::f(); }

Again the expression becomes dependent and will be resolved during the second phase. The main difference is that in this second case, the call is qualified and thus it does not use dynamic dispatch. If A<T>::f() was virtual it would still execute A<T>::f(), and not the final overrider.


Is the code correct? No. Does VS accept it? Yes.

This is a known non-conformance in the Visual Studio compiler, that does not implement two phase lookup. It delays all lookup inside the template to the second phase and at that point lookup succeeds.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Another way is a `using A::f;` declaration, or my favored idiom, `using B::A::f;`. – Potatoswatter Oct 02 '13 at 05:29
  • Why does compiler not know about `A` when compiling `B`? If the latter inherits from `A`, shouldn't the compiler first compile `A` and only after that start resolving the call inside `B`? – Kolay.Ne Mar 11 '22 at 09:55
  • @Kolay.Ne: `A` might be specialized for some `T` to not have `f`, and it would be weird to have it suddenly refer to a global `f` in that case. – Davis Herring Mar 11 '22 at 15:13
  • @DavisHerring, yes it would, just as it is weird that now it refers to the global `f`. However, I agree, seems like the way they do it now is better. But still, I think it still was possible to make things more clear. For example, compiler could produce a warning on such a call unless the namespace (either global or `A`'s) is explicitly specified – Kolay.Ne Mar 12 '22 at 16:10
  • @Kolay.Ne: The point is that the current behavior is consistent and hence teachable. A warning would be a denial of that teachability in favor of a claim that a dependent base class ought to invalidate unqualified lookup (outside the template) altogether—even leaving aside the question of operators, would you really want to write `::std::size_t`? – Davis Herring Mar 12 '22 at 16:57
  • @DavisHerring, of course I don't suggest to give a warning for every single lookup that doesn't have an explicit namespace. But if there is a method with the same signature both in the base template class and in the global namespace, I think it is worth specifying the namespace explicitly in such cases, so it could've been suggested by compiler warnings – Kolay.Ne Mar 15 '22 at 05:24
  • @Kolay.Ne: One could warn in the case where the base class is a specialization of a *non-dependent* class template whose primary template has a matching name, but in general the signature and SFINAE might depend on the template arguments, and individual specializations *shouldn’t* warn since you might be inheriting from an unrelated class whose members are deliberately irrelevant. – Davis Herring Mar 15 '22 at 14:59