Reputation: 4915
I want to make a benchmark
function that, given an invocable and its parameters, will time the execution of the given invocable and return the measured std::chrono::duration<...>
as well as the value returned by the invocation of the invocable.
I am having problems with perfect forwarding the value returned from the invocation. Currently I return the value returned by the invocation and use a reference parameter to return the duration:
using bench_clock = std::conditional_t<std::chrono::high_resolution_clock::is_steady,
std::chrono::high_resolution_clock, std::chrono::steady_clock>;
decltype(auto) benchmark(
bench_clock::duration& duration, auto&& func, auto&&... args)
{
auto begin{ bench_clock::now() };
decltype(auto) result{
std::invoke(
std::forward<decltype(func)>(func),
std::forward<decltype(args)>(args)...)
};
duration = bench_clock::now() - begin;
return result;
}
As far as I know, this perfectly forwards the value returned from the invocation.
I would prefer to also return the duration conventionally, as an example, by using std::tuple
though I am not sure how to do it t perfectly forward the returned value.
My guess would be to use std::invoke_result_t
like this:
using bench_clock = std::conditional_t<
std::chrono::high_resolution_clock::is_steady,
std::chrono::high_resolution_clock, std::chrono::steady_clock>;
auto benchmark(auto&& func, auto&&... args)
{
auto begin{ bench_clock::now() };
decltype(auto) result{
std::invoke(
std::forward<decltype(func)>(func),
std::forward<decltype(args)>(args)...)
};
auto duration{ bench_clock::now() - begin };
return std::tuple<std::invoke_result_t<decltype(func), decltype(args)...>, bench_clock::duration>{std::forward<decltype(result)>(result), duration};
//------------------------------------------------------------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ is this needed?
}
I am not sure if this approach correctly perfectly forwards. I also don't know if is required to use std::forward
in the std::tuple
constructor.
There is also a problem, that if the invocable returns void
, the tuple cannot be used, as std::tuple<void>
cannot be instantiated.
I am not sure how to go around this.
Upvotes: 3
Views: 309
Reputation: 32722
Yes, you are right. You need to handle the void
return case when you invoke the function func
inside the benchmark
function.
Since you have used std::invoke_result_t
and std::conditional_t
, I assume that you have access to c++17. Then this can be easily solved by an if constexpr
checking.
#include <iostream>
#include <chrono>
#include <tuple>
#include <type_traits> // std::is_same_v, std::invoke_result_t
#include <functional> // std::invoke
#include <utility> // std::forward, std::move
using namespace std::chrono;
using bench_clock = std::conditional_t<
high_resolution_clock::is_steady, high_resolution_clock, steady_clock>;
template<typename Func, typename... Args>
decltype(auto) benchmark(Func&& func, Args&&... args)
{
auto begin{ bench_clock::now() };
if constexpr (std::is_same_v<void, std::invoke_result_t<Func, Args...>>)
{
// can not have void result: therefore just invoke the func!
std::invoke(std::forward<Func>(func), std::forward<Args>(args)...);
return bench_clock::now() - begin; // only the time duration will be returned!
}
else
{
// all other cases return the tuple of result-duration!
decltype(auto) result{ std::invoke(std::forward<Func>(func), std::forward<Args>(args)...) };
const auto duration{ bench_clock::now() - begin };
return std::make_tuple(std::move(result), duration);
}
}
Upvotes: 3