3

I do need std::function for stateful operation.

So I would like to make an instance of a class implementing operator() into std::function. I did test std::bind works, but can't wrap it up as a function.

struct StatefulOperation
{
    StatefulOperation(int num) : num_(num) {}
    int operator()(int i)
    {
        num_ += i;
        return num_;
    }
    
    int num_;
};


// This works

const StatefulOperation obj(40);
    
using std::placeholders::_1;
std::function<int(int)> op = std::bind(&StatefulOperation::operator(), obj, _1);


// Want something like this

template<typename A, typename B, typename C>
std::function<B(A)> op(C& obj);


// This doesn't work

template<typename A, typename B, typename C>
std::function<B(A)> op(C& obj)
{
    using std::placeholders::_1;
    return std::bind(&C::operator(), obj, _1);
}

The last implementation attempt fails with error like below;

main.cpp:52:21: note:   template argument deduction/substitution failed:
main.cpp:75:35: note:   couldn’t deduce template parameter ‘A’
   75 |     std::function<int(int)> f = op(a);
      |                                 ~~^~~

How can I get this done?

Aamir
  • 1,974
  • 1
  • 14
  • 18
lighthouse
  • 413
  • 2
  • 10

3 Answers3

2

The compiler cannot deduce A and B just from the return type. You need to specify them:

std::function<int(int)> f = op<int, int>(a);

However, you don't really need these additional template parameters. Just use auto to deduce the return type:

template<typename C>
auto op(C& obj)
{
    using std::placeholders::_1;
    return std::bind(&C::operator(), std::ref(obj), _1);
}

The return type will not be exactly the same, but convertible to what you want (std::function<B(A)>). If you want to specify the exact return type, you can do so with a combination of this and std::invoke_result_t.

Nelfeal
  • 12,593
  • 1
  • 20
  • 39
  • perhaps worth mentioning that `auto` will also not deduce the right types for the `std::function` but the type returned can be converted to `std::function` – 463035818_is_not_an_ai Jul 04 '23 at 13:05
1

Instead of using bind you should use lambda expressions. Also you StatefulOperation should NOT be const (otherwise it can't update its internal state).

#include <functional>
#include <iostream>

struct StatefulOperation
{
    StatefulOperation(int num) : num_(num) {}
    int operator()(int i)
    {
        num_ += i;
        return num_;
    }

    int num_;
};

int main()
{
    /*const*/ StatefulOperation obj(40); // your state cannot be const since you are changing its internal state
    
    // instead of bind rely on lambda expressions (bind is still there because of well... history)
    std::function<int(int)> op{ [&](int num) { return obj(num); } };

    for (int i = 0; i < 10; ++i)
    {
        std::cout << op(i) << " ";
    }

    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
0

Since your class is a callable that implements a function call operator with the expected types, you can initialize a std::function in the most simple way:

std::function<int(int)> op{StatefulOperation(40)};

Do not forget that the state is copied when op is copied.

j6t
  • 9,150
  • 1
  • 15
  • 35