问题
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 byFoos
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.
回答1:
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 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);
}
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
回答2:
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.
回答3:
Sure! Although of course, default arguments won't work.
Approach the problem as one of recursive list processing. The simplest algorithm is to peel the Args
and Foos
typelist while repeating one step:
- If the next
Foos
can be called with the current set of arguments, then call it. Proceed with the next entry inFoos
and the current list ofArgs
. - Otherwise, add the next entry in
Args
to the current set of arguments.
Keep everything packaged in tuple
s for convenience. The best practice is to obtain a tuple of references by std::forward_as_tuple
. By passing around complete tuples, you don't need to "explicitly list" either of them, as you mentioned.
/* Entry point: initialize the function and argument counters to <0, 0>. */
template< typename foos, typename args > // foos and args are std::tuples
void split_and_call( foos f, args a ) {
split_and_call_impl< 0, 0 >( 0, std::move( f ), std::move( a ) );
}
// fx = function (foo) index, ax = argument index, cur = current arg list.
template< std::size_t fx, std::size_t ax, typename ... cur,
typename foos, typename args >
// Use expression SFINAE to cancel this overload if the function cannot be called.
decltype( std::declval< std::tuple_element_t<fx,
// Be careful to keep std::tuple_element in bounds.
std::enable_if_t< fx < std::tuple_size< foos >::value, foos
> > >()( std::declval< cur >() ... ) )
split_and_call_impl( int, foos && f, args && a, cur && ... c ) {
// We verified this call will work, so do it.
std::get< fx >( f )( std::forward< cur >( c ) ... );
// Now proceed to the next function.
split_and_call_impl< fx + 1, ax >( 0, std::move( f ), std::move( a ) );
}
// Similar, but simpler SFINAE. Only use this if there's an unused argument.
// Take "char" instead of "int" to give preference to first overload.
template< std::size_t fx, std::size_t ax, typename ... cur,
typename foos, typename args >
std::enable_if_t< ax < std::tuple_size< args >::value >
split_and_call_impl( char, foos && f, args && a, cur && ... c ) {
// Try again with one more argument.
split_and_call_impl< fx, ax + 1 >( 0, std::move( f ), std::move( a ),
std::forward< cur >( c ) ..., std::get< ax >( std::move( a ) ) );
}
// Terminating case. Ensure that all args were passed to all functions.
template< std::size_t fx, std::size_t ax, typename foos, typename args >
std::enable_if_t< ax == std::tuple_size< args >::value
&& fx == std::tuple_size< foos >::value >
split_and_call_impl( int, foos && f, args && a ) {}
Live demo.
If you have control over the Foos
, then you might consider letting each Foo
accept an initial subsequence of arguments, and pass the remainder along to the next Foo
as a pack.
回答4:
Here's a fairly short solution, that also stores all the return values of each functor (if any) in a tuple:
#include <iostream>
#include <utility>
#include <tuple>
template <typename F> struct ArgumentSize;
template <typename R, typename... Args>
struct ArgumentSize<R(Args...)> : std::integral_constant<std::size_t, sizeof...(Args)> {};
template <typename R, typename C, typename... Args>
struct ArgumentSize<R(C::*)(Args...) const> : ArgumentSize<R (Args...)> {};
template <typename R, typename C, typename... Args>
struct ArgumentSize<R(C::*)(Args...)> : ArgumentSize<R (Args...)> {};
template <typename C>
struct ArgumentSize : ArgumentSize<decltype(&C::operator())> {};
// etc...
struct NoReturnValue {
friend std::ostream& operator<< (std::ostream& os, const NoReturnValue&) {
return os << "[no return value]";
}
};
template <std::size_t Offset, typename F, typename Tuple, std::size_t... Is>
auto partial_apply_with_offset (F f, const Tuple& tuple, const std::index_sequence<Is...>&,
std::enable_if_t<!std::is_void<std::result_of_t<F(std::tuple_element_t<Offset+Is, Tuple>...)>>::value>* = nullptr) {
return f(std::get<Offset+Is>(tuple)...);
}
template <std::size_t Offset, typename F, typename Tuple, std::size_t... Is>
auto partial_apply_with_offset (F f, const Tuple& tuple, const std::index_sequence<Is...>&,
std::enable_if_t<std::is_void<std::result_of_t<F(std::tuple_element_t<Offset+Is, Tuple>...)>>::value>* = nullptr) {
f(std::get<Offset+Is>(tuple)...);
return NoReturnValue();
}
template <std::size_t Offset, typename TupleArgs>
std::tuple<> invoke (const TupleArgs&) {return std::tuple<>();}
template <std::size_t Offset, std::size_t First, std::size_t... Rest, typename TupleArgs, typename F, typename... Fs>
auto invoke (const TupleArgs& tupleArgs, F f, Fs... fs) {
const auto singleTuple = std::make_tuple (partial_apply_with_offset<Offset> (f, tupleArgs, std::make_index_sequence<First>{}));
return std::tuple_cat (singleTuple, invoke<Offset + First, Rest...>(tupleArgs, fs...));
}
template <typename... Fs, typename... Args>
auto splitAndCall (const Args&... args) {
const std::tuple<Args...> tuple(args...);
return invoke<0, ArgumentSize<Fs>::value...>(tuple, Fs{}...);
}
// Testing
struct F {
int operator()(int x, char y) const { std::cout << "F(" << x << "," << y << ')' << std::endl; return 0; }
};
struct G {
void operator()(double x) const { std::cout << "G(" << x << ')' << std::endl; }
};
struct H {
double operator()() const { std::cout << "H()" << std::endl; return 3.5; }
};
int main() {
const std::tuple<int, NoReturnValue, double> t = splitAndCall<F,G,H>(1,'a',3.5); // F(1,a) G(3.5) H()
std::cout << std::get<0>(t) << ' ' << std::get<1>(t) << ' ' << std::get<2>(t) << '\n'; // 0 [no return value] 3.5
}
来源:https://stackoverflow.com/questions/32288778/build-template-from-arguments-of-functions