Motivation:
std::apply
takes single tuple like argument and unpacks the tuple into the function call. The limitation is that one has to know if the argument is tuple like or not, which makes its use cases severed.
I found a way to solve the problem having 100% backwards compatibility (at least I believe so). The code below didn't break any of the benchmarks I've written before, using benchmark v2, so it is tested:
Code:
template <typename T>
struct is_straight_tuple : public std::false_type
{};
template <typename ... Ts>
struct is_straight_tuple<std::tuple<Ts...>> : public std::true_type
{};
template <typename T>
struct is_std_array : public std::false_type
{};
template <typename T, std::size_t size>
struct is_std_array<std::array<T, size>> : public std::true_type
{};
template <typename T>
struct is_tuple_like : public std::bool_constant<is_std_array<T>::value || is_straight_tuple<T>::value>
{};
namespace detail
{
template <typename Callable, typename Tuple>
decltype(auto) genuine_apply(Callable&& callable, Tuple&& tuple, std::true_type)
{
return std::apply(std::forward<Callable>(callable), std::forward<Tuple>(tuple));
}
template <typename Callable, typename Tuple>
decltype(auto) genuine_apply(Callable&& callable, Tuple&& tuple, std::false_type)
{
return std::forward<Callable>(callable)(std::forward<Tuple>(tuple));
}
}
template <typename Callable, typename T>
decltype(auto) genuine_apply(Callable&& callable, T&& argument)
{
return detail::genuine_apply(std::forward<Callable>(callable), std::forward<T>(argument),
is_tuple_like<std::decay_t<T>>{});
};
The usage is the same as of std::apply<>()
(callables
is a tuple of functions to be benchmarked):
auto callable_input = gen(input); //separate input creation from benchmark
auto start = std::chrono::high_resolution_clock::now();
shino::genuine_apply(std::get<Index>(callables), callable_input);
auto end = std::chrono::high_resolution_clock::now();
Some simpler tests:
#include <stdexcept>
int dummy_x(const std::tuple<int, int>&)
{
return 1;
}
int dummy_y(int y)
{
return y;
}
void check(int retvalue, int expectedvalue)
{
if (retvalue != expectedvalue)
{
throw std::logic_error("genuine apply doesn't return the correct value");
}
}
int main()
{
int res = 0;
res = shino::genuine_apply(dummy_x, std::make_tuple(std::tuple<int, int>(1, 1)));
check(res, 1);
res = shino::genuine_apply(dummy_y, 1);
check(res, 1);
shino::genuine_apply(dummy_y, std::make_tuple(1));
check(res, 1);
}
The solution is based on this SO question.