I have stumbled upon something that I don't quite understand. I have a class hierarchy that uses private inheritance where each of the struct
s 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?
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
.