Search code examples
c++boostshared-ptrderived-class

Using boost::function with a parameter to shared pointer to derived class


Using C++ with g++ 5.4.0 on Ubuntu 16.04.

I have a class A, and a class B that derives from class A. Function f1 takes a shared pointer to class A as parameter. Function f2 takes a shared pointer to class B as parameter and return the same type as f1. Using boost::function, another function F takes a function such as f1 as parameter. The code looks like this :

result_t f1 ( const boost::shared_ptr<A> a );
result_t f2 ( const boost::shared_ptr<B> b );
typedef boost::function < result_t (const boost::shared_ptr<A>&) > f1_t;
void F ( const f1_t f );

Calling F with f1 as argument works fine. I now want to call F but with f2 as argument. I get the following error :

error: invalid initialization of reference of type const boost::shared_ptr<B>& from expression of type const boost::shared_ptr<A>
           return f(BOOST_FUNCTION_ARGS);

There's not really a need for F to get this error, doing :

f1_t f = f2;

gives the same error.


Solution

  • There's no co-variance for function prototypes. Different signatures are simply that: different types.

    In this case you'd need to wrap the function with a converting wrapper.

    Let's create a few facility definitions:

    using result_t = int;
    struct A { };
    struct B : A { };
    
    typedef boost::shared_ptr<A> APtr;
    typedef boost::shared_ptr<B> BPtr;
    
    result_t f1(APtr) { return 1; }
    result_t f2(BPtr) { return 2; }
    
    typedef boost::function <result_t(APtr const&)> funOfA;
    typedef boost::function <result_t(BPtr const&)> funOfB;
    

    Now wrapping funOfB as a funOfA would look like this:

    funOfA wrapFunOfB(const funOfB f) {
        struct {
            funOfB _f;
            result_t operator()(APtr const& a) const { 
                return _f(boost::static_pointer_cast<B>(a));
            }
        } wrap { f };
    
        return wrap;
    }
    

    Now you can easily write:

    int main() {
        F(f1);
        F(wrapFunOfB(f2));
    }
    

    Simple C++03 Demo

    Live On Coliru

    #include <boost/shared_ptr.hpp>
    #include <boost/make_shared.hpp>
    #include <boost/function.hpp>
    #include <iostream>
    
    typedef int result_t;
    struct A { int i; };
    struct B : A { int j; };
    
    typedef boost::shared_ptr<A> APtr;
    typedef boost::shared_ptr<B> BPtr;
    
    result_t f1(APtr) { return 1; }
    result_t f2(BPtr) { return 2; }
    
    typedef boost::function <result_t(APtr const&)> funOfA;
    typedef boost::function <result_t(BPtr const&)> funOfB;
    
    struct Wrapper {
        typedef result_t result_type;
        funOfB _f;
    
        result_t operator()(APtr const& a) { 
            return _f(boost::static_pointer_cast<B>(a));
        }
    };
    
    funOfA wrapFunOfB(const funOfB f) {
        Wrapper wrap = { f };
        return wrap;
    }
    
    void F(const funOfA f) {
        APtr a = boost::make_shared<A>();
        APtr b = boost::make_shared<B>();
    
        //std::cout << "f(a): " << f(a) << "\n"; // UNDEFINED BEHAVIOUR if f wraps a funOfB
        std::cout << "f(b): " << f(b) << "\n";
    }
    
    int main() {
        F(f1);
        F(wrapFunOfB(f2));
    }
    

    Prints

    f(b): 1
    f(b): 2
    

    PROBLEMS, WARNINGS: dynamic_pointer_cast<>

    If F actually invokes the parameter on an object that isn't actually of type B, that static_cast<> will invoke Undefined Behaviour.

    If you want to protect against that, use dynamic_pointer_cast, which requires the classes A and B to be polymorphic types.

    Live On Coliru

    #include <boost/shared_ptr.hpp>
    #include <boost/make_shared.hpp>
    #include <boost/function.hpp>
    #include <iostream>
    
    typedef int result_t;
    struct A     { int i; virtual ~A() {} };
    struct B : A { int j; };
    
    typedef boost::shared_ptr<A> APtr;
    typedef boost::shared_ptr<B> BPtr;
    
    result_t f1(APtr a) { return a?1 : 0; }
    result_t f2(BPtr b) { return b?2 : -99; }
    
    typedef boost::function <result_t(APtr const&)> funOfA;
    typedef boost::function <result_t(BPtr const&)> funOfB;
    
    struct Wrapper {
        typedef result_t result_type;
        funOfB _f;
    
        result_t operator()(APtr const& a) { 
            return _f(boost::dynamic_pointer_cast<B>(a));
        }
    };
    
    funOfA wrapFunOfB(const funOfB f) {
        Wrapper wrap = { f };
        return wrap;
    }
    
    void F(const funOfA f) {
        APtr a = boost::make_shared<A>();
        APtr b = boost::make_shared<B>();
    
        std::cout << "f(a): " << f(a) << "\n";
        std::cout << "f(b): " << f(b) << "\n";
    }
    
    int main() {
        F(f1);
        F(wrapFunOfB(f2));
    }
    

    Prints

    f(a): 1
    f(b): 1
    f(a): -99
    f(b): 2
    

    C++11 Version

    Things get a little more elegant here. Notably, the Wrapper class can be local, and anonymous:

    funOfA wrapFunOfB(const funOfB f) {
        struct {
            typedef result_t result_type;
            funOfB _f;
    
            result_t operator()(APtr const& a) { 
                return _f(std::dynamic_pointer_cast<B>(a));
            }
        } wrap { f };
        return wrap;
    }
    

    Next level: use a lambda instead:

    funOfA wrapFunOfB(const funOfB f) {
        return [f](APtr const& a) { return f(std::dynamic_pointer_cast<B>(a)); };
    }
    

    Live On Coliru

    #include <memory>
    #include <functional>
    #include <iostream>
    
    typedef int result_t;
    struct A     { int i; virtual ~A() {} };
    struct B : A { int j; };
    
    typedef std::shared_ptr<A> APtr;
    typedef std::shared_ptr<B> BPtr;
    
    result_t f1(APtr a) { return a?1 : 0; }
    result_t f2(BPtr b) { return b?2 : -99; }
    
    typedef std::function<result_t(APtr const&)> funOfA;
    typedef std::function<result_t(BPtr const&)> funOfB;
    
    funOfA wrapFunOfB(const funOfB f) {
        return [f](APtr const& a) { return f(std::dynamic_pointer_cast<B>(a)); };
    }
    
    void F(const funOfA f) {
        APtr a = std::make_shared<A>();
        APtr b = std::make_shared<B>();
    
        std::cout << "f(a): " << f(a) << "\n";
        std::cout << "f(b): " << f(b) << "\n";
    }
    
    int main() {
        F(f1);
        F(wrapFunOfB(f2));
    }
    

    That's 25% code reduction.