c++stdbindprivate-inheritance

Binding to privately inherited member function


I'd like to std::bind to a member function from a private base class, made "public" with a using-declaration in the derived class. Calling the function directly works, but it seems binding or using member function pointers doesn't compile:

#include <functional>

struct Base {
    void foo() { }
};

struct Derived : private Base { 
    using Base::foo;            
};

int main(int, char **)
{
    Derived d;

    // call member function directly:
    // compiles fine
    d.foo();

    // call function object bound to member function:
    // no matching function for call to object of type '__bind<void (Base::*)(), Derived &>'
    std::bind(&Derived::foo, d)();

    // call via pointer to member function:
    // cannot cast 'Derived' to its private base class 'Base'
    (d.*(&Derived::foo))();

    return 0;
}

Looking at the error messages above, the issue seems to be that Derived::foo is still just Base::foo, and I can't access Base through Derived outside Derived itself.

This seems inconsistent - should I not be able to use direct calls, bound functions, and function pointers interchangeably?

Is there a workaround that would let me bind to foo on a Derived object, preferably without changing Base or Derived (which are in a library I don't own)?


Solution

  • The issue here is what the using-declaration actually does:

    struct Derived : private Base { 
        using Base::foo;            
    };
    

    That brings Base::foo into public scope in Derived, but it doesn't create an entirely new function. It is not equivalent to having written:

    struct Derived : private Base {
        void foo() { Base::foo(); }
    }
    

    There is still only Base::foo(). The using-declaration simply affects the access rules and the overload resolution rules. As such &Derived::foo really has type void (Base::*)() (and not void (Derived::*)()!), since that is the only foo that exists. Since Base is private, member access through a pointer to Base is ill-formed. I agree that this is pretty unfortunate ("inconsistent" is a good word).

    You can still create a function object that calls foo. You just can't use the pointer to member. With C++14, this becomes straightforward if verbose (I'm assuming arbitrary arguments here and that void foo() is merely a simplification of the problem):

    auto d_foo = [d](auto&&... args){ return d.foo(std::forward<decltype(args)>(args)...); }
    

    With C++11, you'd have to write a type with a variadic template operator().