I am trying to write a generic function dispatch mechanism where I do some additional work before calling the actual function (e.g. time the function's execution). The following code works, except for functions of the type void f(....) since we are declaring ret.
#define execute(fn, ...) exec_helper(#fn, fn, ##__VA_ARGS__)
#define execute0(fn) exec_helper(#fn, fn)
template <typename TASK, typename... ARGS>
auto exec_helper(const char *fn_name, TASK&& task, ARGS&&... args)
{
//std::function<std::result_of_t<TASK(ARGS...)> ()> func
// = std::bind(std::forward<TASK>(task),
// std::forward<ARGS>(args)...);
#ifdef TIME_FUNC
auto start = std::chrono::steady_clock::now();
#endif
auto ret = task(std::forward<ARGS>(args)...);
#ifdef TIME_FUNC
auto end = std::chrono::steady_clock::now();
auto diff = end - start;
auto time = std::chrono::duration<double, std::milli>(diff).count();
std::cout << "\n" << fn_name << "\t = "
<< std::setprecision(3) << time << " ms\n";
#endif
return ret;
}
Is there a way to make it work for these type of functions as well? Perhaps using some template tricks?
One of the interesting feature of C++, is that you can write this:
return f();
even if the return type of f is void. In this way, you can actually avoid using ret variable.
Keeping this in mind, and how destructor works, you can wrap the work-before-task and work-after-task in a class — and call them in the constructor and destructor respectively, as shown below:
struct watch
{
extern watch(char const *fn_name)
: _fn_name(fn_name),
_start(std::chrono::steady_clock::now()) {}
~watch()
{
auto end = std::chrono::steady_clock::now();
auto diff = end - _start;
auto time = std::chrono::duration<double, std::milli>(diff).count();
std::cout << "\n" << _fn_name << "\t = "
<< std::setprecision(3) << time << " ms\n";
}
char const *_fn_name;
decltype(std::chrono::steady_clock::now()) _start;
};
and use it as:
template <typename TASK, typename... ARGS>
auto exec_helper(const char *fn_name, TASK&& task, ARGS&&... args)
{
#ifdef TIME_FUNC
watch start_watching { fn_name };
#endif
return task(std::forward<ARGS>(args)...);
}
Hope that helps.
You can even generalize the wrapper class as:
struct invariant_executor //choose a more general name now!
{
using F = std::function<void()>;
invariant_executor(F before, F after): _after(std::move(after))
{
before();
}
~invariant_executor()
{
_after();
}
F _after;
};
and use it as:
template <typename TASK, typename... ARGS>
auto exec_helper(const char *fn_name, TASK&& task, ARGS&&... args)
{
#ifdef TIME_FUNC
decltype(std::chrono::steady_clock::now()) start;
invariant_executor local
{
[&] { start = std::chrono::steady_clock::now(); },
[&] {
auto end = std::chrono::steady_clock::now();
auto diff = end - start;
auto time = std::chrono::duration<double, std::milli>(diff).count();
std::cout << "\n" << fn_name << "\t = "
<< std::setprecision(3) << time << " ms\n";
}
};
#endif
return task(std::forward<ARGS>(args)...);
}
Now you've the flexibility to execute any code in the constructor and the destructor of invariant_executor.
You could use a packaged_task:
std::packaged_task<Sig> pack_task(std::forward<Task>(task));
auto fut = pack_task.get_future();
// pre-time stuff
// run task
pack_task(std::forward<Args>(args)...);
// post-time stuff
// get the result - works with either void or a real type
return fut.get();
That already takes care of the void special case, although now you'd have to come up with Sig, and deal with the overhead of constructing pack_task.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With