任意功能的计时器

时间:2014-06-28 15:39:42

标签: c++ templates c++11 variadic-templates chrono

我尝试构建一个可以测量任意类型函数执行时间的函数模板。这是我到目前为止所尝试的:

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

请注意,我必须为void(...)函数使用不同名称的函数。有没有办法摆脱第二个功能?

(首先我做的是正确的吗?)

4 个答案:

答案 0 :(得分:4)

在这种情况下,我会将持续时间作为对函数调用包装器的引用传递:

#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';
}

你可以将它包装在一个类中:

#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';
}

注意:从函数调用返回void是明确定义的:void a() { return b(); } void b()

答案 1 :(得分:4)

您可以使用enable_if或标记调度。 Enable_if似乎是这种情况下更快捷的方式:

#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
}

此外,您应该使用完美转发将参数传递给被调用函数:

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

当你调用函数时:

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

Demo。请注意,这适用于函数,lambda和可调用对象;几乎所有内容都有operator()

从设计的角度来看,我认为返回std::pair没有问题。由于C ++ 11具有std::tie,因此返回pair / tuple是从函数返回多个结果的合法方式。我会继续说,为了在void情况下保持一致,你应该返回一个只有一个元素的元组。

答案 2 :(得分:0)

只是超载它。此外,您应该更改功能签名,如下所示。 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 };
}

对于void

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

    fn(args...);

    //...

    return elapsed_seconds.count();
}

然而,它不适用于lambdas。

非捕获lambda函数有一个workaround(它可以强制推广)

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

然后你可以像这样传递lambdas:

timer(to_function_pointer([](){

    // Lambda function

}));

答案 3 :(得分:0)

C ++ 14通用lambda删除了使用模板的需要。我在 Effective Modern C ++ 中看到的代码片段演示了这一点:

auto timeFuncInvocation = 
    [](auto&& func, auto&&... params)
    {
        start timer; 
        std::forward<decltype(func)>(func)(
            std::forward<decltype(params)>(params)...); 
        stop timer and record elapsed time; 
    };