Reputation: 50063
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?)
Upvotes: 7
Views: 1566
Reputation: 31519
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;
};
Upvotes: 0
Reputation: 11181
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.
Upvotes: 5
Reputation:
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()
Upvotes: 4
Reputation: 56479
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
}));
Upvotes: 0