7

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?)

Constructor
  • 7,273
  • 2
  • 24
  • 66
Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
  • 1
    No it is not correct (gentle) - you should make a simple working test case with all include and a main –  Jun 28 '14 at 15:47
  • 2
    A code not tested is a code not working – YePhIcK Jun 28 '14 at 15:49
  • @YePhIcK It worked with examples, but that does not mean it is correct. – Baum mit Augen Jun 28 '14 at 15:52
  • 2
    I think you should post this at http://codereview.stackexchange.com. – R Sahu Jun 28 '14 at 15:56
  • @RSahu I think it belongs here because it does not work the way I intend it to (i.e. not need two distinct names). I do not seek advice on my style etc. (although it is always welcome of course). Edit: Re-reading the rules of codereview, it seems to be on-topic there. So if fits better on there, could it me moved? – Baum mit Augen Jun 28 '14 at 15:59
  • [This](http://stackoverflow.com/a/21995693/2567683) may be of help – Nikos Athanasiou Jun 28 '14 at 16:55
  • @NikosAthanasiou While the link you gave provides a very similar solution, that solution does not handle the return value of the function. If it's non-void, the return value is simply ignored, which is not what the OP seems to want. – cmaster - reinstate monica Jun 28 '14 at 17:07
  • I guess, you could achieve the behavior that you need by use of `std::enable_if<>`. This template is, for example, used to enable implicit conversion of smart pointers in the STL only if the underlying pointer types can be converted implicitly. – cmaster - reinstate monica Jun 28 '14 at 17:14

4 Answers4

5

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.

sbabbi
  • 11,070
  • 2
  • 29
  • 57
  • You forgot to change the return type for the `void` case from `pair` to `double`. But I like this solution most so far. – Baum mit Augen Jun 28 '14 at 18:33
  • Remarks: By returning a `tuple` in both cases, I can use `std::get<0>` to access the time without worrying about the return type. I could use `std::is_void` instead of `std::is_same` to get a little bit cleaner code. The template arguments for `std::forward` can be deduced automatically. All in all just what I was looking for. – Baum mit Augen Jun 28 '14 at 19:19
  • 1
    @BaummitAugen Cool I didn't recall `is_void`. You are wrong for the automatically deduction of the argument of `std::forward`; you need it otherwise it would just deduce lvalue references every time. – sbabbi Jun 28 '14 at 20:01
4

In this case I would pass the duration as a reference to the the wrapper of the function call:

#include <chrono>
#include <iostream>
#include <thread>

template <typename Duration, class Fn, class... Args>
auto call(Duration& duration, Fn fn, Args... args) -> decltype(fn(args...)) {

    using namespace std::chrono;

    struct DurationGuard {
        Duration& duration;
        high_resolution_clock::time_point start;
        DurationGuard(Duration& duration)
        :   duration(duration),
            start(high_resolution_clock::now())
        {}
        ~DurationGuard() {
            high_resolution_clock::time_point end = high_resolution_clock::now();
            duration = duration_cast<Duration>(end - start);
        }
    };

    DurationGuard guard(duration);
    return fn(args...);
}

void f() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

int g() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 42;
}

int main () {

    using namespace std::chrono;

    duration<double> s;
    call(s, f);
    std::cout << s.count() << '\n';

    milliseconds ms;
    int n = call(ms, g);
    std::cout << ms.count() << ", " << n << '\n';
}

You may wrap it up in a class:

#include <chrono>
#include <iostream>
#include <thread>

template <typename Duration = std::chrono::duration<double>>
class InvokeDuration
{
    public:
    template<typename Fn, class... Args>
    auto operator () (Fn fn, Args... args) -> decltype(fn(args...)) {
        using namespace std::chrono;
        struct Guard {
            Duration& duration;
            high_resolution_clock::time_point start;
            Guard(Duration& duration)
            :   duration(duration),
                start(high_resolution_clock::now())
            {}
            ~Guard() {
                high_resolution_clock::time_point end = high_resolution_clock::now();
                duration = duration_cast<Duration>(end - start);
            }
        };
        Guard guard(m_duration);
        return fn(args...);
    }

    const Duration& duration() const { return m_duration; }
    typename Duration::rep count() const { return m_duration.count(); }

    private:
    Duration m_duration;
};

void f() {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

int g(int n) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    return n;
}

int main () {
    InvokeDuration<> invoke;
    invoke(f);
    std::cout << invoke.count() << '\n';
    int n = invoke(g, 42);
    std::cout << invoke.count() << ", " << n << '\n';
}

Note: Returning void from a function call is well defined: void a() { return b(); } with void b()

0

Just overload it. Also, you should change the function signature as below. Live code.

template <typename R, typename... Args>
auto timer(R (*fn)(Args...), Args... args) -> std::pair<double, R>
{
    //...

    auto ret = fn(args...);

    //...

    return { elapsed_seconds.count(), ret };
}

And for void:

template <typename... Args>
auto timer(void (*fn)(Args...), Args... args) -> double
{
    //...

    fn(args...);

    //...

    return elapsed_seconds.count();
}

However it doesn't work for lambdas.

 

There is a workaround for non-capturing lambda functions (which brakes the generalization).

template <typename Function>
struct function_traits
  : public function_traits<decltype(&Function::operator())>
{};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
  typedef ReturnType (*pointer)(Args...);
  typedef std::function<ReturnType(Args...)> function;
};

template <typename Function>
typename function_traits<Function>::pointer
to_function_pointer (const Function& lambda)
{
  return static_cast<typename function_traits<Function>::pointer>(lambda);
}

and then you can pass lambdas like this:

timer(to_function_pointer([](){

    // Lambda function

}));
masoud
  • 55,379
  • 16
  • 141
  • 208
  • Is your function signature better than mine in general or only for the sake of overloading? – Baum mit Augen Jun 28 '14 at 16:48
  • It's better for overloading and template type deduction. I also added a live working code. – masoud Jun 28 '14 at 16:49
  • Sadly, that does not seem to work for lambdas ([live](http://coliru.stacked-crooked.com/a/aa851e4fbb7dee46)). – Baum mit Augen Jun 28 '14 at 16:55
  • Yes, I've noticed and mentioned it in the answer. Yes, lambdas are too wild for them. I've written a workaround for lambdas, you can see it. – masoud Jun 28 '14 at 17:13
  • If you are going for the overloading way, it is way better to use sfinae or tag dispatching than this. Also not every lambda is implicitly castable to a function pointer. – sbabbi Jun 28 '14 at 18:06
0

C++14 generic lambdas remove the need to use templates. A code snippet I saw in Effective Modern C++ demonstrates this :

auto timeFuncInvocation = 
    [](auto&& func, auto&&... params)
    {
        start timer; 
        std::forward<decltype(func)>(func)(
            std::forward<decltype(params)>(params)...); 
        stop timer and record elapsed time; 
    };
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153