Skip to content

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

Open in Compiler Explorer ↗ Open SHOW_BUG variant ↗ Quiz this entry