Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I tried to build a function template that can measure the execution time of functions of arbitrary type. Here is what I've tried so far:

#include <chrono>
#include <iostream>
#include <type_traits>
#include <utility>

// Executes fn with arguments args and returns the time needed
// and the result of f if it is not void
template <class Fn, class... Args>
auto timer(Fn fn, Args... args)
    -> std::pair<double, decltype(fn(args...))> {
  static_assert(!std::is_void<decltype(fn(args...))>::value,
                "Call timer_void if return type is void!");
  auto start = std::chrono::high_resolution_clock::now();
  auto ret = fn(args...);
  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed_seconds = end - start;
  return { elapsed_seconds.count(), ret };
}

// If fn returns void, only the time is returned
template <class Fn, class... Args>
double timer_void(Fn fn, Args... args) {
  static_assert(std::is_void<decltype(fn(args...))>::value,
                "Call timer for non void return type");
  auto start = std::chrono::high_resolution_clock::now();
  fn(args...);
  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed_seconds = end - start;
  return elapsed_seconds.count();
}

int main () {
    //This call is ambigous if the templates have the same name
    std::cout << timer([](double a, double b){return a*b;},1,2).first;
}

Notice that I have to have a function with a different name for void(...) functions. Is there any way to get rid of the second function?

(And is what I did correct in the first place?)

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
157 views
Welcome To Ask or Share your Answers For Others

1 Answer

You can use enable_if or tag dispatching. Enable_if seems to be the quicker way in this case:

#include <type_traits>

template <class Fn, class... Args>
auto timer(Fn fn, Args && ... args) -> typename std::enable_if< 
    // First template argument is the enable condition
    !std::is_same< 
            decltype( fn( std::forward<Args>(args) ... )), 
            void >::value,
    // Second argument is the actual return type
    std::pair<double, decltype(fn(std::forward<Args>(args)...))> >::type
{
   // Implementation for the non-void case
}

template <class Fn, class... Args>
auto timer(Fn fn, Args &&... args) -> typename std::enable_if< 
    std::is_same< 
            decltype( fn( std::forward<Args>(args) ... )), 
            void >::value,
    double>::type
{
   // Implementation for void case
}

Also you should use perfect forwarding to pass the arguments to the called function:

 auto timer(Fn fn, Args && ... args) // ...
                      ~~~^   

And when you call the function:

 auto ret = fn( std::forward<Args>(args)...);

Demo. Notice that this works with functions, lambda and callable objects; pretty much everything with an operator().

From a design standpoint, I see no problem in returning a std::pair. Since C++11 has std::tie, returning a pair/ tuple is the legitimate way of returning multiple results from a function. I would go forward and say that for consistency in the void case you should return a tuple with only one element.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...