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?

+3


source to share


5 answers


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

.

+5


source


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

.

+1


source


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

.

0


source


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

      

0


source


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

      

0


source







All Articles