General function dispatch mechanism
I am trying to write a generic function dispatch mechanism where I do additional work before the actual function is called (like the run time of the function). The following code works, except for type functions 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 types of functions as well? Perhaps using some boilerplate tricks?
source to share
One of the interesting things about C ++ is that you can write this:
return f();
even if the return type f
is void
. This way you can avoid using a variable ret
.
With that in mind and how the destructor works, you can wrap work before the task and work after the task in the 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 like:
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 it 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 like:
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)...);
}
You now have the flexibility to execute any code in your constructor and destructor invariant_executor
.
source to share
You can use 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();
This already takes care of the special case void
, although now you'll have to come up with Sig
and handle the creation overhead pack_task
.
source to share
You can use std::packaged_task
and wait std::future
to become available. Of course this will have an overhead.
template<typename Func, typename ... Args>
auto func(Func &&f, Args &&... args) -> decltype(f(std::forward<Args>(args)...)){
using return_type = decltype(f(std::forward<Args>(args)...));
std::packaged_task<return_type(Args&&...)> task(f);
auto future = task.get_future();
// do something
task(std::forward<Args>(args)...);
// do something else
return future.get();
}
Then your function can have any return type, even void
.
source to share
You can send exec_helper()
to one of two helper functions exec_helper2()
, depending on whether the return type is invalid or not. These functions overload each other, but differ in the type of their first parameter isvoid
.
#include <chrono>
#include <iomanip>
#include <iostream>
#include <utility>
#include <type_traits>
#define TIME_FUNC
#define execute(fn, ...) exec_helper(#fn, fn, ##__VA_ARGS__)
#define execute0(fn) exec_helper(#fn, fn)
template <typename TASK, typename... ARGS>
auto exec_helper2(std::false_type isbool,
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;
}
template <typename TASK, typename... ARGS>
auto exec_helper2(std::true_type isbool,
const char *fn_name, TASK&& task, ARGS&&... args)
{
#ifdef TIME_FUNC
auto start = std::chrono::steady_clock::now();
#endif
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
}
template <typename TASK, typename... ARGS>
auto exec_helper(const char *fn_name, TASK&& task, ARGS&&... args)
{
return exec_helper2(std::is_same<std::result_of_t<TASK(ARGS...)>, void>{},
fn_name, std::forward<TASK>(task),
std::forward<ARGS>(args)...);
}
void void_f() {}
int int_f() { return 42; }
int main()
{
execute0(void_f);
execute0(int_f);
}
source to share
Something along the following lines might work for you:
#include <iostream>
template <typename Func, typename T> class ResultStash {
public:
explicit ResultStash(Func f) : m_t(f()) {}
T get() { return std::move(m_t); }
private:
T m_t;
};
template <typename Func> class ResultStash<Func, void> {
public:
explicit ResultStash(Func f) { f(); }
void get() { return; }
};
template <typename Func>
auto wrap_function_execution(Func f) -> decltype(f()) {
std::cout << "Doing something before executing the function" << std::endl;
// Call f() and remember the result
ResultStash<decltype(f), decltype(f())> result(f);
std::cout << "Doing something after executing the function" << std::endl;
// Return the result of f()
return result.get();
}
void someVoidFunction(int * a) {
if (a) {
*a = 5;
std::cout << "Setting a = " << *a << std::endl;
}
}
int someNonVoidFunction(int a) {
int b = 10;
std::cout << "Multiplying " << a << " by " << b << std::endl;
return a * b;
}
int main(int argc, char * argv[]) {
int a;
wrap_function_execution([&a] { someVoidFunction(&a); });
auto b = wrap_function_execution([&a] { return someNonVoidFunction(a); });
std::cout << "b = " << b << std::endl;
return 0;
}
This introduces a template class that contains the result of the base function in your wrapper and is specialized for void functions. Using std :: move in the non-void version of the class should minimize the overhead of "releasing" the hidden result in your wrapper.
This has the following (minor) advantages over Nawaz:
- It keeps the stream of your wrapper code in one place, rather than breaking it down into a wrapper class constructor and destructor.
- This avoids working in the destructor.
- It is easier to interact with the wrapped function as a result of the wrapper code that follows the execution.
It has the following disadvantages:
- The result of the wrapped function should be movable (although this is usually fine).
- It is more intrusive, i.e. more difficult to comment on.
- The Navaz approach is more attractive in terms of RAII.
In the example code:
Doing something before executing the function
Setting a = 5
Doing something after executing the function
Doing something before executing the function
Multiplying 5 by 10
Doing something after executing the function
b = 50
source to share