I have following problem:
I want to create variadic template class, that must call lambda, that may take first N parameters of template parameter pack (N may vary from 0 to the size of parameters pack and differs for different lambda).
I think, that recursive template helper function, that will check if lambda is invokable, starting with all parameters, should be the best variant, but how can I pass all but last parameters from parameters pack to it and is it possible at all?
At the moment I see it as something like this:
template< typename Callable, typename ...Args >
void Call( Callable& cb, Args... args )
{
using Function = std::remove_reference_t< Callable >;
if constexpr( std::is_invocable_v< Function, Args... > )
cb( args... );
else if constexpr( sizeof...( args ) == 0 )
cb();
else
{
// Here I want to create shortened_args, that are all args but last one.
// Call( cb, shortened_args... );
}
}
UPD: I didn't mention it first, but I need C 17 solution.
CodePudding user response:
You might use std::index_sequence
as helper:
template <typename Callable, typename Tuple, std::size_t... Is>
constexpr std::size_t is_invocable_args(std::index_sequence<Is...>)
{
return std::is_invocable_v<Callable, std::tuple_element_t<Is, Tuple>...>;
}
// Helper to know max number of args to take from tuple
template <typename Callable, typename Tuple, std::size_t... Is>
constexpr std::size_t max_invocable_args(std::index_sequence<Is...>)
{
std::array<bool, sizeof...(Is)> a = {
is_invocable_args<Callable, Tuple>(std::make_index_sequence<Is>{})...
};
auto rit = std::find(a.rbegin(), a.rend(), true);
if (rit == a.rend()) throw "no valid call";
return std::distance(a.begin(), rit.base()) - 1;
}
template <std::size_t N, typename Callable, typename Tuple>
constexpr std::size_t max_invocable_args()
{
return max_invocable_args<Callable, Tuple>(std::make_index_sequence<N>{});
}
// Call taking some element from tuple
template< typename Callable, std::size_t... Is, typename Tuple >
void Call_Tuple_impl(Callable cb, std::index_sequence<Is...>, Tuple&& args )
{
std::invoke(cb, std::get<Is>(std::forward<Tuple>(args))...);
}
template< typename Callable, typename ...Args >
void Call( Callable cb, Args... args )
{
constexpr std::size_t max_arg_count = max_invocable_args<sizeof...(Args), Callable, std::tuple<Args...>>();
Call_Tuple_impl(cb, std::make_index_sequence<max_arg_count>{}, std::forward_as_tuple(std::forward<Args>(args)...));
}
C 20 Demo
C 17 Demo with rewritten constexpr
find
.
CodePudding user response:
Break it into two pieces. First, determine how many arguments. Then invoke with that many arguments.
template<class Tup, std::size_t...Is>
auto slice_tuple(Tup tup, std::index_sequence<Is...>){
using std::get;
return std::make_tuple(get<Is>(std::forward<Tup>(tup))...);
}
template<std::size_t N, class Tup>
auto front_of_tuple(Tup tup){
return slice_tuple(std::forward<Tup>(tup), std::make_index_sequence<N>{});
}
pack up the args into a tuple, get the front N, then std apply.
To perfect forward/avoid copies, use reference wrappers.
CodePudding user response:
C 17 solution:
template <typename Callable, typename Tuple, std::size_t... Is>
void Call_impl(Callable& cb, Tuple t, std::index_sequence<Is...>) {
if constexpr (sizeof...(Is) == 0 && !std::is_invocable_v<Callable>)
throw "no valid call";
else if constexpr (std::is_invocable_v<
Callable, std::tuple_element_t<Is, Tuple>...>)
cb(std::get<Is>(t)...);
else
Call_impl(cb, std::move(t),
std::make_index_sequence<sizeof...(Is) - 1>());
}
template<typename Callable, typename... Args>
void Call(Callable cb, Args... args) {
Call_impl(cb, std::tuple(std::move(args)...),
std::index_sequence_for<Args...>());
}