从函数参数构建模板?

Build template from arguments of functions?

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 而不使用 Args 可以完成吗?即使我确实明确列出了它们,我实际上也不确定该怎么做。

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

我试图组合一个非递归实例化版本,但它涉及一些当前不存在的实用程序。

split_and_call

假设我们有 F 需要 2 ints,G 需要 1 int 和参数 1, 2, 3.

给定FGtuple(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>{}).

扩展 FGFns{}... 很简单,用 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

std::应用

我们需要的部分实际上是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>;

切片

[Begin, End) 范围内对 index_sequence 进行切片。

例如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;
};

Full solution on Ideone

奖金

我将分享这一部分,因为我为了好玩而进一步玩这个。我不会讲太多细节,因为这不是要求的内容。

  • 将语法更新为 call(fs...)(args...); 以便可以传递顶级函数等。例如call(f, g)(1, 2, 3)
  • 将每个函数调用的结果返回为 std::tuple。例如auto result = call(f, g)(1, 2, 3)

Full solution on Ideone

好的!当然,默认参数将不起作用。

将问题作为递归列表处理之一来处理。最简单的算法是在重复一个步骤的同时剥离 ArgsFoos 类型列表:

  • 如果下一个 Foos 可以用当前参数集调用,则调用它。继续 Foos 中的下一个条目和 Args 中的当前列表。
  • 否则,将 Args 中的下一个条目添加到当前参数集。

为方便起见,将所有内容打包在 tuple 中。最佳实践是通过 std::forward_as_tuple 获取引用的元组。通过传递完整的元组,您不需要 "explicitly list" 它们中的任何一个,正如您提到的那样。

/*  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.

如果您可以控制 Foos,那么您可以考虑让每个 Foo 接受参数的初始子序列,并将余数作为参数传递给下一个 Foo包。

这是一个相当简短的解决方案,它还将每个函子(如果有的话)的所有 return 值存储在一个元组中:

#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
}