54 Intermediate The Hundred
One Overload Hides Them All
add one overload, lose the rest
#include <iostream>
#include <string>
struct Printer {
void print(int n) { std::cout << "int: " << n << "\n"; }
void print(const std::string& s) { std::cout << "string: " << s << "\n"; }
};
struct PrettyPrinter : Printer {
void print(double d) { std::cout << "double: " << d << "\n"; }
};
int main() {
PrettyPrinter p;
p.print(42); // the int overload... right?
#ifdef SHOW_BUG
p.print("hello"); // the string overload... right?
#endif
}
Run it. What does it print?
Answer
double: 42 — the int overload never enters the race; 42 is quietly converted. And the
string call (behind -DSHOW_BUG) doesn't compile at all.
Why¶
Name lookup walks scopes from the inside out and stops at the first scope that contains
the name. Finding print in PrettyPrinter, it never looks into Printer: a derived
print(double) hides all base print overloads, matching or not. Overload resolution
then sees one candidate, so 42 converts and "hello" has nowhere to go:
error: cannot convert ‘const char [6]’ to ‘double’
note: initializing argument 1 of ‘void PrettyPrinter::print(double)’
The hiding is perfectly legal — -Wall -Wextra stays silent, and so does
-Woverloaded-virtual, which only guards virtual functions.
The fix¶
A using-declaration re-imports every base overload; the double one joins the set:
struct PrettyPrinter : Printer {
using Printer::print; // the base overloads are back
void print(double d) { std::cout << "double: " << d << "\n"; }
};
Now p.print(42) prints int: 42 and p.print("hello") prints string: hello.
Takeaway: overloading never crosses a scope boundary — a derived f hides every
base f, so bring them back with using Base::f;.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo — then add -DSHOW_BUG