2

I want to save a generic callable with its state for later use. Please see the example code bellow. I could probably use std::function or std::bind to achieve this but I do not know what is best. Also please note that in the main() of the example below, the capturedInt must be saved in the state of the callable.

What are the possibilities for:

  • makeCallable(fun, args...) { ... }
  • CallableType
template <typename RetT>
class Service
{
public:

   template <typename Fn, typename... Args>
   Service(Fn&& fun, Args&&... args)
   {
      m_callable = makeCallable(fun, args...);
   }

   Run()
   {
      m_callable();
   }

   CallableType<RetT> m_callable;
};

// Template deduction guides (C++17)
template <typename Fn, typename... Args>
Service(Fn&& fun, Args&&... args) -> Service<std::invoke_result_t<std::decay_t<Fn>, std::decay_t<Args>...>>;

int main()
{
   Service* s = nullptr;
   {
      int capturedInt = 5;
      s = new Service([capturedInt]() { std::cout << capturedInt << std::endl; } );
   }
   
   s->Run();
}

pettitpeon
  • 87
  • 4

3 Answers3

2

I would also use std::function, but leave it as the interface to the class, like this:

template <typename RetT>
class Service
{
public:

   Service(std::function<RetT()> fun)
   {
      m_callable = std::move(fun);
   }

   Run()
   {
      m_callable();
   }
private:
   std::function<RetT()> m_callable;
};

Then you're explicit about the options to store callables for the class. The user can then decide how to bind their arguments to the callable themselves, which with std::function is flexible.

s = new Service([capturedInt]() { std::cout << capturedInt << std::endl; } );
s->Run();

or

struct Foo
{
    void MyPrint(int capturedInt) { std::cout << capturedInt << std::endl; }
};
Foo foo;
int capturedInt = 5;
s = new Service(std::bind(&Foo::MyPrint, &foo, capturedInt);
s->Run();

. Then you don't have to worry about life time problems caused by the class.

Mikael H
  • 1,323
  • 7
  • 12
1

Given the setup, your only option for m_callable would be an std::function. Since the type of the functor is an argument to the constructor itself, you'd have to type-erase the functor to save it for future use - and std::function is just a mechanism for this.

As a result, m_callable would be:

std::function<retT ()> m_callable;

and you would set it like that:

m_callable = [=]() { return fun(args...); }
SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • Why not use `auto`? – Coral Kashri Jun 23 '20 at 16:44
  • 1
    While I agree with std::function being the only type if `Fn` and `Args...` can differ for one return type, your construction of `m_callable` does not make use of perfect forwarding. For example, you couldn't pass a move-only type like `str::unique_ptr`. I would actually use `std::bind` since perfect forwarding arguments and the function for a call and through a lambda capture is non-trivial. But `m_callable = std::bind(std::forward(fun), std::forrward(args)...);` should work like a treat. – n314159 Jun 23 '20 at 16:45
0

Besides std::function<> and std::bind, there's a pattern called continuation passing. The benefit is, you can also save a generic lambda, something that wouldn't work with the former two; the price you pay is, you need to wrap the rest of the code in a lambda:

template<typename Cont>
void get_plus2_cps(Cont cont) {
    auto plus2 = [&](auto n) { return n + 2; };
    cont(plus2);
}

// usage:
int main() {
    // some code
    get_plus2_cps([&](auto plus2) {
        // rest of the code here
        std::cout << plus2(2) << ' ' << plus2(2.0) << std::endl;
    };
    // no code here
}
lorro
  • 10,687
  • 23
  • 36