A clean way to store a function and its (arbitrary type, arbitrary number) arguments

For the library, I would like the function to take another function and its arguments, and then save them for calling later. Arguments must allow any type mix, but functions should only return void. Something like that:

void myFunc1(int arg1, float arg2); void myFunc2(const char *arg1); class DelayedCaller { ... public: static DelayedCaller *setup(Function func, …); }; ... DelayedCaller* caller1 = DelayedCaller::setup(&myFunc1, 123, 45.6); DelayedCaller* caller2 = DelayedCaller::setup(&myFunc2, "A string"); caller1->call(); // Calls myFunc1(), with arguments 123 and 45.6 caller2->call(); // Calls myFunc2(), with argument "A string" 

One approach is to force DelayedCaller :: setup () to accept the std :: function, and have my library users use std :: bind () before calling setup (). However, is there a way to implement setup () so that users do not need to bind themselves?

Edit : DelayedCaller is an existing class. setup () is a new static method that I would like to add.

+5
source share
4 answers

It is possible to use variation templates and call std::bind() from the setup() function:

 #include <iostream> #include <string> #include <functional> #include <memory> void myFunc1(int arg1, float arg2) { std::cout << arg1 << ", " << arg2 << '\n'; } void myFunc2(const char *arg1) { std::cout << arg1 << '\n'; } class DelayedCaller { public: template <typename TFunction, typename... TArgs> static std::unique_ptr<DelayedCaller> setup(TFunction&& a_func, TArgs&&... a_args) { return std::unique_ptr<DelayedCaller>(new DelayedCaller( std::bind(std::forward<TFunction>(a_func), std::forward<TArgs>(a_args)...))); } void call() const { func_(); } private: using func_type = std::function<void()>; DelayedCaller(func_type&& a_ft) : func_(std::forward<func_type>(a_ft)) {} func_type func_; }; int main() { auto caller1(DelayedCaller::setup(&myFunc1, 123, 45.6)); auto caller2(DelayedCaller::setup(&myFunc2, "A string")); caller1->call(); caller2->call(); return 0; } 

Output:

  123, 45.6
 A string

Return a smart pointer, such as std::unique_ptr , instead of returning a raw pointer (or returning by value and avoiding dynamic allocation. func_type is movable if the arguments move or can be pretty cheap to copy anyway. You might need to define the move constructor and move the assignment operator, they will be created under certain conditions).

+7
source

You can use lambda functions to hide the bindings:

 #include <functional> class DelayedCaller : public std::function< void(void) > { public: DelayedCaller(std::function< void(void) > fn) : std::function< void(void) >(fn) {} }; DelayedCaller caller1([]() { myFunc1(123, 45.6); }); DelayedCaller caller2([]() { myFunc2("A string"); }); caller1(); // Calls myFunc1(), with arguments 123 and 45.6 caller2(); // Calls myFunc2(), with argument "A string" 

It also gives library users more flexibility. They are not limited to a single function call, and functions have access to the source environment in which they were created:

 int x; DelayedCaller caller3 = [&x]() { if (x == 0) DoSomething(); else DoSomethingElse(); }; 
+7
source

If you want / can use the C ++ 11 future library, you can use std::async

 #include <future> auto caller = std::async(myFunc1, 123, 45.6); // Creates a future object. caller.get(); // Waits for the function to get executed and returns result. 

To use lazy rating:

 auto caller = std::async(std::launch::deferred, myFunc1, 123, 45.6); 

It also has the advantage that a function call can be made in another thread that uses multi-core equipment. However, this may not be acceptable in each case.

0
source

If your only problem is to hide argument binding from callsite when saving your interface, use variable templates

 class DelayedCaller { public: template<typename... Args> static DelayedCaller* setup(void (functionPtr*)(Args...), Args&&... args) { return new DelayedCaller(std::bind(functionPtr, std::forward<Args>(args)...)); } DelayedCaller(const std::function<void()>& f) : f(f) {} private: std::function<void()> f; }; 

The open constructor still offers your users the ability to initialize it with a lambda if they wish.

0
source

Source: https://habr.com/ru/post/950826/


All Articles