Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic function dispatch mechanism

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?

like image 973
Omair Avatar asked May 27 '26 14:05

Omair


2 Answers

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.

like image 84
Nawaz Avatar answered May 30 '26 05:05

Nawaz


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.

like image 35
Barry Avatar answered May 30 '26 03:05

Barry



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!