Создать шаблон из аргументов функций?
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
? Я на самом деле не уверен, как это сделать, даже если я явно перечислил их обоих.
4 ответа
Я попытался собрать нерекурсивную инстанцирующую версию, но она включает в себя несколько утилит, которые в настоящее время не существуют.
split_and_call
Предположим, у нас есть F
который занимает 2 int
с и 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.
Предположим, что наши функции [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);
}
коммунальные услуги
- std:: apply (C++ 17)
- function_arity
- make_index_range
- ломтик
- partial_sum
станд:: применять
Часть, которая нам нужна, на самом деле 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))...);
}
function_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_range
Вариация на make_index_sequence<N>
какие конструкции 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
В диапазоне [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>());
}
partial_sum
Функциональная версия 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
, напримерauto result = call(f, g)(1, 2, 3)
Эскиз был дан @TC выше. Предполагая, что указатели на функции передаются, 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)...));
}
(Плохое название, но что угодно). Демо
Конечно! Хотя, конечно, аргументы по умолчанию не будут работать.
Подход к проблеме как к одной из рекурсивных обработок списков. Самый простой алгоритм - очистить 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
как стая
Вот довольно короткое решение, которое также хранит все возвращаемые значения каждого функтора (если есть) в кортеже:
#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
}