How Name Hiding Affects Overloaded Functions in C++
In C++, declaring a member function in a derived class hides all functions with the identical name in the base class, regardless of their parameter types, return types, or const-qualification. This article explains how C++ name lookup rules cause base class overloads to disappear from candidate sets during compilation, the errors this behavior produces, and the standard techniques used to restore base class overloads into derived scope.
The Mechanism: Name Lookup Precedes Overload Resolution
To understand name hiding, it is necessary to examine how the C++ compiler processes a function call. The compilation process separates name lookup from overload resolution:
- Name Lookup: The compiler searches scopes starting from the local scope and moves outward. When resolving a member function on a derived class instance, it first inspects the derived class's scope.
- Scope Termination: As soon as the compiler finds at least one declaration matching the requested function name in the current scope, the lookup stops immediately. It does not continue searching enclosing scopes, such as base classes.
- Overload Resolution: Overload resolution occurs only among the functions found during the name lookup phase.
Because the search halts at the derived class boundary, any function with the same name declared in a base class is omitted from the overload candidate set.
Demonstrating Name Hiding
Consider the following scenario where a base class provides multiple overloads for handling different data types:
class Base {
public:
void display(int value);
void display(double value);
};
class Derived : public Base {
public:
void display(const std::string& text);
};When invoking display through an instance of
Derived:
Derived d;
d.display("Hello"); // Compiles successfully: Derived::display(const std::string&)
d.display(42); // Compilation error: No matching function for call to 'Derived::display(int)'The call d.display(42) fails to compile. The compiler
does not fall back to Base::display(int) because name
lookup finds Derived::display(const std::string&) and
stops. Overload resolution then attempts to match the integer argument
42 against the single candidate
display(const std::string&), which cannot accept an
integer without a valid conversion.
Impact on Virtual Functions
Name hiding also affects virtual functions. If a base class contains several overloads of a virtual function and a derived class overrides only one of them, the non-overridden overloads become hidden from the derived class interface.
class Base {
public:
virtual void process();
virtual void process(int x);
};
class Derived : public Base {
public:
void process() override; // Hides Base::process(int)
};Calling d.process(10) on a Derived instance
will fail to compile for the same reason:
Base::process(int) is hidden by
Derived::process().
Solutions to Name Hiding
There are two primary ways to resolve name hiding depending on the context:
1. The using
Declaration
The standard way to unhide base class functions is to introduce them
into the derived class scope using the using keyword. This
makes all overloads from the base class available for overload
resolution alongside the derived class's own declarations.
class Derived : public Base {
public:
using Base::display; // Brings Base::display(int) and Base::display(double) into scope
void display(const std::string& text);
};With this declaration, d.display(42) compiles without
issue because the compiler treats all three overloads as members of
Derived during overload resolution.
2. Explicit Scope Resolution
If modifying the derived class definition is not possible or desired, the caller can bypass derived class name lookup by qualifying the function call with the base class name:
Derived d;
d.Base::display(42); // Explicitly calls Base::display(int)This syntax disables dynamic dispatch if used on virtual functions,
but it directly directs the compiler to look in Base for
the identifier, resolving the hidden overload issue at the call
site.