Search code examples
c++oopinheritancestructusing

C++ 'using' keyword in class hierarchy with function call operator and private inheritance


I have stumbled upon something that I don't quite understand. I have a class hierarchy that uses private inheritance where each of the structs defines a different function call operator. Oddly enough, the function call operator from the topmost struct is available in the most derived struct, despite the fact that a using directive is only used in the first derived struct. A regular function foo, though, is not accessible there, as expected. Example:

#include <string>
#include <vector>
#include <iostream>

struct A {
    void foo() {}
    void operator()(bool) {
        std::cout << "bool\n";
    }
};

struct B : private A {
    using A::foo;
    using A::operator();
    
    void operator()(std::string) {}
};

struct C : private B {
    using B::operator();
    
    void operator()(std::vector<int>) {}
};

struct D : private C {
    using C::operator();
    
    void operator()(std::vector<double>) {}
};

int main() {

    D d{};

    d(false);  // <-- works!
    //d.foo(); // <-- error: ‘void A::foo()’ is private within this context

    return 0;
}

I happened upon this while trying to implement the C++17 overload object for use with boost::apply_visitor using pre-C++17 code. I solved it using recursive inheritance, where each object pulls in the function call operator of its direct base class like so:

    template<typename T, typename... Ts>
    struct visitor : private T, private visitor<Ts...> {
        using T::operator();
        using visitor<Ts...>::operator();

        visitor(T func, Ts... tail) : T{ std::move(func) }, visitor<Ts...>{ std::move(tail)... } {}
    };

    template<typename T>
    struct visitor<T> : private T {
        using T::operator();

        visitor(T func) : T{ std::move(func) } {}
    };

    template<typename... Ts>
    visitor<Ts...> make_visitor(Ts&&... funcs) {
        return visitor<Ts...>{ std::forward<Ts>(funcs)... };
    }

I wanted to understand why all of the operators are available in the most derived object. That's how I came up with the above example. Compiler is g++ 11.1.0.

Can anyone enlighten me as to what's going on here?


Solution

  • As pointed out by others in comments, it turns out I just had an error in my thinking. The using pulls in all the operators available in the respective base class, including the ones that were imported by the base class itself, and therefore all the operators will be available in the bottommost object. foo, on the other hand, is just handed down to B.