Создать шаблон из аргументов функций?

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_sequences.

Предположим, что наши функции [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;
};

Полное решение на Ideone

бонус

Я поделюсь этой частью, так как я играл с ней дальше для развлечения. Я не буду вдаваться в подробности, так как это не то, что просили.

  • Обновлен синтаксис для call(fs...)(args...); так что функции верхнего уровня, например, могут быть переданы. например call(f, g)(1, 2, 3)
  • Возвращает результаты каждого из вызовов функций как std::tuple, например auto result = call(f, g)(1, 2, 3)

Полное решение на Ideone

Эскиз был дан @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
}
Другие вопросы по тегам