从函数的参数构建模板?

时间:2015-08-29 17:21:16

标签: c++ c++14 variadic-templates template-meta-programming

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
  • 的arity
  • ArgsFoos
  • 使用的所有参数类型的串联

这可以仅使用Foos而不是 Args来完成吗?即使我明确地列出了它们,我实际上也不确定该怎么做。

4 个答案:

答案 0 :(得分:7)

我尝试将非递归实例化版本放在一起,但它涉及一些当前不存在的实用程序。

split_and_call

假设我们F需要2 int s,G需要1 int1, 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>{})

使用F扩展GFns{}...很简单,使用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);
}

实用程序

  • 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

用于确定函数的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>的{​​{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>

partial_sum

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>

Full solution on Ideone

加成

我会分享这部分,因为我为了好玩而进一步玩这个。我不会详细介绍,因为它不是被问到的。

  • 将语法更新为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

Full solution on Ideone

答案 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)

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

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

  • 如果可以使用当前参数集调用下一个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 ) {}

Live demo.

如果您可以控制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
}