C++ Quiz79 of 173
Difficulty:
⬅️ Prev: 179Quiz ID: 184Next: 185 ➡️

According to the C++23 standard, what is the output of this program?

#include <iostream>

struct Base {
  void f(int) { std::cout << "i"; }
};

struct Derived : Base {
  void f(double) { std::cout << "d"; }
};

int main() {
  Derived d;
  int i = 0;
  d.f(i);
}
Hint

What are the scopes of the two names f?

Reveal answer

d

Explanation

Why does overload resolution not pick the obviously better void f(int), but instead void f(double) which requires a floating-integral conversion from int to double?

It turns out that void f(int) isn't even found by name lookup, and is not considered for overload resolution at all. When the name f is introduced in Derived, it hides the name f that was introduced in Base.

Let's explore the details about how the name f is looked up in a class scope C:

§[class.member.lookup]¶3:

The declaration set is the result of a single search in the scope of C for N from immediately after the class-specifier of C if P is in a complete-class context of C (...). If the resulting declaration set is not empty, the subobject set contains C itself, and calculation is complete.

So when looking for a declaration of f and finding Derived::f, the calculation is complete, and Base is not examined at all. If no f was found in Derived, we would continue with §[class.member.lookup]¶4:

Otherwise (...), S(N,C) is initially empty. Calculate the lookup set for N in each direct non-dependent (§[temp.dep.type]) base class subobject Bi, and merge each such lookup set S(N,Bi) in turn into S(N,C).

But since Derived does indeed contain a declaration of f, we never get around to looking at Base.

⬅️ Prev: 179Quiz ID: 184Next: 185 ➡️