Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Build template from arguments of functions?

template<class... Foos> // N = sizeof...(Foos)
template<typename... Args> // M = sizeof...(Args)
void split_and_call(Args&&... args)
{
    // Using Python notation here...
    Foos[0](*args[:a]);  // a = arity of Foos[0]
    Foos[1](*args[a:b]); // b-a = arity of Foos[1]
    ...
    Foos[N-1](*args[z:M]); // M-z = arity of Foos[N-1]
}

Assumptions:

  • All types in Foos are callable
  • All types in Foos are unambiguous
  • Any type in Foos may have an arity of 0
  • Args is the concatenation of all of the argument types used by Foos

Can this be done with just Foos and without Args? I'm actually not sure how to do it even if I did explicitly list them both.

like image 846
Brian Rodriguez Avatar asked Dec 20 '22 00:12

Brian Rodriguez


2 Answers

I've tried to put together a non-recursive instantiating version, but it involves a few utilities that don't currently exist.

split_and_call

Suppose we have F which takes 2 ints, and G that takes 1 int and arguments 1, 2, 3.

Given F, G, tuple(1, 2, 3), index_sequence<0, 1>, index_sequence<2>, we want to call apply_impl(F{}, tuple(1, 2, 3), index_sequence<0, 1>{}) and apply_impl(G{}, tuple(1, 2, 3), index_sequence<2>{}).

Expanding the F, G is simple with Fns{}..., and making the tuple of arguments is also simple with std::forward_as_tuple(std::forward<Args>(args)...). We're left to construct the index_sequences.

Suppose our function arities are [2, 1, 3], we first get the partial sum of this and prepend a 0: [0, 2, 3, 6]. The index ranges we want are: [0, 2), [2, 3), [3, 6).

We split [0, 2, 3, 6] into is = [0, 2, 3], and js = [2, 3, 6] and zip them to get the ranges we want.

template <typename... Fns, typename Args, std::size_t... Is, std::size_t... Js>
void split_and_call_impl(Args &&args,
                         std::index_sequence<Is...>,
                         std::index_sequence<Js...>) {
  int dummy[] = {
      (apply_impl(Fns{}, std::forward<Args>(args), make_index_range<Is, Js>{}),
       0)...};
  (void)dummy;
}

template <typename... Fns, typename... Args>
void split_and_call(Args &&... args) {
  auto partial_sums = partial_sum_t<0, function_arity<Fns>{}...>{};
  auto is = slice<0, sizeof...(Fns)>(partial_sums);
  auto js = slice<1, sizeof...(Fns) + 1>(partial_sums);
  split_and_call_impl<Fns...>(
      std::forward_as_tuple(std::forward<Args>(args)...), is, js);
}

Utilities

  • std::apply (C++17)
  • function_arity
  • make_index_range
  • slice
  • partial_sum

std::apply

The part we need is actually the apply_impl part.

template <typename Fn, typename Tuple, size_t... Is>
decltype(auto) apply_impl(Fn &&fn, Tuple &&tuple, std::index_sequence<Is...>) {
  return std::forward<Fn>(fn)(std::get<Is>(std::forward<Tuple>(tuple))...);
}

function_arity

Used to determine the arity of a function.

template <typename F>
struct function_arity;

template <typename R, typename... Args>
struct function_arity<R (Args...)>
    : std::integral_constant<std::size_t, sizeof...(Args)> {};

template <typename R, typename... Args>
struct function_arity<R (*)(Args...)> : function_arity<R (Args...)> {};

template <typename R, typename... Args>
struct function_arity<R (&)(Args...)> : function_arity<R (Args...)> {};

template <typename R, typename C, typename... Args>
struct function_arity<R (C::*)(Args...) const> : function_arity<R (Args...)> {};

template <typename R, typename C, typename... Args>
struct function_arity<R (C::*)(Args...)> : function_arity<R (Args...)> {};

template <typename C>
struct function_arity : function_arity<decltype(&C::operator())> {};

make_index_range

A variation on make_index_sequence<N> which constructs index_sequence<0, .. N>. make_index_range<B, E> constructs index_sequence<B, .. E>.

template <typename T, typename U, T Begin>
struct make_integer_range_impl;

template <typename T, T... Ints, T Begin>
struct make_integer_range_impl<T, std::integer_sequence<T, Ints...>, Begin> {
  using type = std::integer_sequence<T, Begin + Ints...>;
};

template <class T, T Begin, T End>
using make_integer_range =
    typename make_integer_range_impl<T,
                                     std::make_integer_sequence<T, End - Begin>,
                                     Begin>::type;

template <std::size_t Begin, std::size_t End>
using make_index_range = make_integer_range<std::size_t, Begin, End>;

slice

Slices an index_sequence in the range [Begin, End).

e.g. slice<0, 2>(index_sequence<2, 3, 4, 5>{}) == index_sequence<2, 3>

template <std::size_t... Is, std::size_t... Js>
constexpr decltype(auto) slice_impl(std::index_sequence<Is...>,
                                    std::index_sequence<Js...>) {
  using array_t = std::array<std::size_t, sizeof...(Is)>;
  return std::index_sequence<std::get<Js>(array_t{{Is...}})...>();
}

template <std::size_t Begin, std::size_t End, std::size_t... Is>
constexpr decltype(auto) slice(std::index_sequence<Is...> is) {
  return slice_impl(is, make_index_range<Begin, End>());
}

partial_sum

Functional version of std::partial_sum.

e.g. partial_sum<2, 3, 4> == index_sequence<2, 5, 9>

template <std::size_t... Is>
struct partial_sum;

template <std::size_t... Is>
using partial_sum_t = typename partial_sum<Is...>::type;

template <>
struct partial_sum<> { using type = std::index_sequence<>; };

template <std::size_t I, std::size_t... Is>
struct partial_sum<I, Is...> {

  template <typename Js>
  struct impl;

  template <std::size_t... Js>
  struct impl<std::index_sequence<Js...>> {
    using type = std::index_sequence<I, Js + I...>;
  };

  using type = typename impl<partial_sum_t<Is...>>::type;
};

Full solution on Ideone

Bonus

I'll share this part since I played with this further for fun. I won't go into too much detail since it's not what was asked.

  • Updated the syntax to call(fs...)(args...); so that top-level functions for example can be passed. e.g. call(f, g)(1, 2, 3)
  • Returned the results of each of the function calls as a std::tuple. e.g. auto result = call(f, g)(1, 2, 3)

Full solution on Ideone

like image 177
mpark Avatar answered Dec 21 '22 15:12

mpark


A sketch was given by @T.C. above. Assuming that function pointers are passed, arity can be simply defined as

template <typename T>
struct arity : arity<std::remove_pointer_t<std::decay_t<T>>> {};
template <typename R, typename... Args>
struct arity<R(Args...)> : std::integral_constant<std::size_t, sizeof...(Args)> {};

The recursive split is then implemented, in C++14, along the lines of

template <std::size_t FI, std::size_t AI, typename... F, typename ArgTuple, std::size_t...indices>
constexpr auto invoke( std::index_sequence<indices...>, std::tuple<F...> const& f, ArgTuple const& args )
  -> std::enable_if_t<FI == sizeof...(F)-1> {
    std::get<FI>(f)(std::get<AI+indices>(args)...);
}
template <std::size_t FI, std::size_t AI, typename... F, typename ArgTuple, std::size_t...indices>
constexpr auto invoke( std::index_sequence<indices...>, std::tuple<F...> const& f, ArgTuple const& args )
  -> std::enable_if_t<FI != sizeof...(F)-1> {
    std::get<FI>(f)(std::get<AI+indices>(args)...);
    invoke<FI+1, AI+sizeof...(indices)>(std::make_index_sequence<arity<std::tuple_element_t<FI+1, std::tuple<F...>>>{}>{}, f, args);
}
template <typename F1, typename... F, typename... Args>
constexpr void invoke( std::tuple<F1, F...> const& f, Args&&... args ) {
    invoke<0, 0>(std::make_index_sequence<arity<F1>{}>{},
                 f, std::forward_as_tuple(std::forward<Args>(args)...));
}

(Bad naming, but whatever). Demo.

like image 35
Columbo Avatar answered Dec 21 '22 15:12

Columbo