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:
Foos
are callableFoos
are unambiguousFoos
may have an arity of 0Args
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.
I've tried to put together a non-recursive instantiating version, but it involves a few utilities that don't currently exist.
Suppose we have F
which takes 2 int
s, 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_sequence
s.
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);
}
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))...);
}
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())> {};
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>;
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>());
}
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
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.
call(fs...)(args...);
so that top-level functions for example can be passed. e.g. call(f, g)(1, 2, 3)
std::tuple
. e.g. auto result = call(f, g)(1, 2, 3)
Full solution on Ideone
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With