Search code examples
c++templatesc++11std-function

passing function objects pointers to functions accepting a pointer to a std::function


I would like to pass functors of different types but identical signatures to a method. Hence I concluded that std::function should be used. But as this method should also store a reference to the function object I want to pass a shared_ptr instead (for lifetime management). The code below works for class B (b.run(...)) but fails to compile for class A (a.run(...) breaks). What is the reason for this conversion problem when a pointer is passed instead of the function object itself and how can I circumvent it?

#include <functional>
#include <memory>

class MyFunctor
{
public:
    void operator()(const float &f)
    {}
};

template<class FunSig>
class A
{
public:
    void run(std::shared_ptr<std::function<FunSig> > f_ptr)
    {
         // store f_ptr in a vector
    }
};

template<class FunSig>
class B
{
public:
    void run(std::function<FunSig> f)
    {}
};

int main()
{
    MyFunctor mf1;
    std::shared_ptr<MyFunctor> mf2_ptr(new MyFunctor);

    A<void (const float &)> a;
    B<void (const float &)> b;

    a.run(mf2_ptr);        // this breaks!
    b.run(mf1);            // this works
}

The compiler error:

error: no matching function for call to ‘A<void(const float&)>::run(std::shared_ptr<MyFunctor>&)’
note: candidate is:
note: void A<FunSig>::run(std::shared_ptr<std::function<FunSig> >) [with FunSig = void(const float&)]
note:   no known conversion for argument 1 from ‘std::shared_ptr<MyFunctor>’ to ‘std::shared_ptr<std::function<void(const float&)> >

Now I discovered that a.run(...) compiles if MyFunctor inherits from std::function:

class MyFunctor : public std::function<void (const float &)>

Why does this work now? I would be nicer if no code changes were necessary in the functors.


Solution

  • Your question is equivalent to asking why this doesn't work:

    struct Integer
    {
        int value;
    };
    
    std::shared_ptr<int> p(new int(1));
    
    std::shared_ptr<Integer> p2 = p;
    

    It doesn't work because they're not the same type. Just because you can store a MyFunctor in a std::function<void(const float&)> doesn't mean that a pointer to one is convertible to a pointer to the other.

    You want:

    auto mf2_ptr = std::make_shared<std::function<void (const float &)>>( MyFunctor() );
    a.run(mf2_ptr);
    

    Now I discovered that a.run(...) compiles if MyFunctor inherits from std::function:

    It compiles because now you can convert shared_ptr<MyFunctor> to shared_ptr<function<void(const float&)>>, but it won't work correctly. std::function::operator()() is not virtual, so if you call the function it will call the base class' operator(), but the base class doesn't point to anything and will throw std::bad_cast.