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]
}
假设:
Foos
中的所有类型都可以调用Foos
中的所有类型都是明确的Foos
中的任何类型都可能具有0 Args
是Foos
这可以仅使用Foos
和而不是 Args
来完成吗?即使我明确地列出了它们,我实际上也不确定该怎么做。
答案 0 :(得分:7)
我尝试将非递归实例化版本放在一起,但它涉及一些当前不存在的实用程序。
假设我们F
需要2 int
s,G
需要1 int
和1, 2, 3
。
鉴于F
,G
,tuple(1, 2, 3)
,index_sequence<0, 1>
,index_sequence<2>
,我们要致电apply_impl(F{}, tuple(1, 2, 3), index_sequence<0, 1>{})
和apply_impl(G{}, tuple(1, 2, 3), index_sequence<2>{})
。
使用F
扩展G
,Fns{}...
很简单,使用std::forward_as_tuple(std::forward<Args>(args)...)
制作参数元组也很简单。我们留下来构建index_sequence
s。
假设我们的函数arities为[2, 1, 3]
,我们首先得到它的部分和,并加上0
:[0, 2, 3, 6]
。
我们想要的索引范围是:[0, 2)
,[2, 3)
,[3, 6)
。
我们将[0, 2, 3, 6]
分为is = [0, 2, 3]
和js = [2, 3, 6]
并将其压缩以获得我们想要的范围。
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);
}
我们需要的部分实际上是apply_impl
部分。
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))...);
}
用于确定函数的arity。
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_sequence<N>
的{{1}}的变体。 index_sequence<0, .. N>
构建make_index_range<B, E>
。
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>;
范围内剪切index_sequence
。
e.g。 [Begin, End)
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>());
}
的功能版。
e.g。 std::partial_sum
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;
};
,以便可以传递顶级函数。例如call(fs...)(args...);
call(f, g)(1, 2, 3)
返回。例如std::tuple
答案 1 :(得分:2)
@ T.C给出了草图。以上。假设传递了函数指针,arity
可以简单地定义为
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)> {};
然后在C ++ 14中按照
的行实现递归拆分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)...));
}
(错误的命名,但无论如何)。 Demo
答案 2 :(得分:1)
当然!当然,默认参数不起作用。
将问题作为递归列表处理之一进行处理。最简单的算法是在重复一步时剥离Args
和Foos
类型列表:
Foos
,则调用它。继续Foos
中的下一个条目以及Args
的当前列表。Args
中的下一个条目添加到当前参数集中。为方便起见,将所有内容打包在tuple
中。最佳实践是通过std::forward_as_tuple
获取引用元组。通过传递完整的元组,你不需要像你提到的那样“明确地列出”它们中的任何一个。
/* 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 ) {}
如果您可以控制Foos
,那么您可以考虑让每个Foo
接受参数的初始子序列,并将余数作为包传递给下一个Foo
。
答案 3 :(得分:1)
这是一个相当简短的解决方案,它还将每个仿函数的所有返回值(如果有)存储在元组中:
#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
}