Unhide Base Class Functions in C++ with using

In C++, declaring a member function in a derived class automatically hides all base class functions sharing that same name, regardless of differences in signatures or return types. This article explains the name hiding mechanism in C++ inheritance and demonstrates how to use the using Base::function_name; syntax to introduce hidden base class functions into the derived class scope, allowing full function overloading across inheritance boundaries.

The Name Hiding Problem in C++

When a derived class defines a member function, the compiler stops searching base classes once it finds the function name in the derived scope. This behavior, known as name hiding or shadowing, suppresses all overloads in the base class—even if their parameter lists differ entirely from the derived class implementation.

class Base {
public:
    void display(int x);
    void display(double x);
};

class Derived : public Base {
public:
    void display(const std::string& str);
};

// Usage:
Derived d;
d.display("Hello"); // Compiles
d.display(42);      // Compilation error: Base overloads are hidden

In the example above, calling d.display(42) fails to compile because Derived::display(const std::string&) hides both Base::display(int) and Base::display(double).

The using Base::function Syntax

To restore access to the base class member functions, introduce a using declaration within the derived class definition.

The general syntax is:

class Derived : public Base {
public:
    using Base::function_name;
    // Derived class overloads or overrides...
};

This declaration brings every overload of function_name from Base into the scope of Derived.

Complete Implementation Example

#include <iostream>
#include <string>

class Base {
public:
    void process(int value) {
        std::cout << "Base::process(int): " << value << '\n';
    }

    void process(double value) {
        std::cout << "Base::process(double): " << value << '\n';
    }
};

class Derived : public Base {
public:
    // Un-hide all overloads of process() from Base
    using Base::process;

    // Add a new overload in the derived class
    void process(const std::string& value) {
        std::cout << "Derived::process(string): " << value << '\n';
    }
};

int main() {
    Derived obj;

    obj.process(10);        // Calls Base::process(int)
    obj.process(3.14);      // Calls Base::process(double)
    obj.process("Example"); // Calls Derived::process(const std::string&)

    return 0;
}

Access Control and Inheritance Rules

  • Access Specifier Placement: The visibility of the un-hidden base functions corresponds to the access section (public, protected, or private) in which the using declaration is placed within the derived class, provided they were accessible to the derived class originally.
  • Exact Signatures: If a derived class defines a function with the exact same parameter types as a base function introduced by using Base::function;, the derived class function overrides or replaces the base function in the derived overload set.
  • Constructors: C++11 introduced a related syntax, using Base::Base;, which uses the same mechanism to inherit all constructors from the base class into the derived class.