Home > Back-end >  How to call function that use part of template parameter pack in c ?
How to call function that use part of template parameter pack in c ?

Time:10-06

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...>());
}

Demo.

  • Related