简体   繁体   中英

C++17 How to save a generic callable for later use

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();
}

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.

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...); }

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM