我尝试构建一个可以测量任意类型函数执行时间的函数模板。这是我到目前为止所尝试的:
#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(...)
函数使用不同名称的函数。有没有办法摆脱第二个功能?
(首先我做的是正确的吗?)
答案 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;
};