从参数包中排除前n个参数

4
我有一个名为foo的函数,它使用可变模板参数将一部分类型传递给调用它的函数bar。例如:
template <typename... T>
void foo() {
  // ...
  template <size_t start_idx, typename... T>
  using param_pack = /*Parameter pack with T[start_idx]...T[N]*/
  auto b = bar<param_pack<2, T...>>();
  // ...
}

有没有一种方法可以提取“子参数包”。在上面的例子中,如果T = [int float char double],那么param_pack<2, T...> = [char double] [编辑] 我的目标是能够使用类似这样的东西来匹配事件处理程序。例如
struct ev {};

template <typename... T>
struct event : ev {
  std::tuple<T...> data_;

  event(T&&... d) : data_(std::make_tuple(std::forward<T>(d)...)) {}
};

template <typename... Functor>
struct handler {
  std::tuple<Functor...> funcs_;

  handler(Functor&&... f) : funcs_(std::make_tuple(std::forward<Functor>(f)...)) {}

  void handle_message(ev* e) {
    auto ptrs = std::make_tuple(
      dynamic_cast<event<param_pack<1, typename function_traits<F>::args>>*>(e)...
    ); 

    match(ptrs);
  }
};

这里的function_traits::args获取了函数参数的参数包,而match循环遍历元组funcs_,检查dynamic_cast是否成功,并执行第一个成功的函数。我已经实现了这些功能。
处理程序类似于:
[] (handler* self, <ARGS>) -> void {
  // ...
}

我基本上是试图摆脱self参数。


一个相关的问题:https://dev59.com/Hmoy5IYBdhLWcg3wa9Uj - keith
糟糕...我的错...现在正在进行编辑。 - ssb
3个回答

4

不考虑索引 N 的检查,这里是一个基于函数声明(不需要定义)和使用声明的可能解决方案:

template<std::size_t N, typename... T, std::size_t... I>
std::tuple<std::tuple_element_t<N+I, std::tuple<T...>>...>
sub(std::index_sequence<I...>);

template<std::size_t N, typename... T>
using subpack = decltype(sub<N, T...>(std::make_index_sequence<sizeof...(T) - N>{}));

这种方法的好处是,您不需要引入一个围绕元组设计并进行迭代特化的新类型。
以下是使用上述代码的最小工作示例:
#include<functional>
#include<tuple>
#include<cstddef>
#include<type_traits>

template<std::size_t N, typename... T, std::size_t... I>
std::tuple<std::tuple_element_t<N+I, std::tuple<T...>>...>
sub(std::index_sequence<I...>);

template<std::size_t N, typename... T>
using subpack = decltype(sub<N, T...>(std::make_index_sequence<sizeof...(T) - N>{}));

int main() {
    static_assert(std::is_same<subpack<2, int, float, char, double>, std::tuple<char, double>>::value, "!");
}

wandbox 上可以看到一个完整的例子。


如果需要对索引 N 进行检查,扩展版本如下:

template<std::size_t N, typename... T, std::size_t... I>
std::enable_if_t<(N < sizeof...(T)), std::tuple<std::tuple_element_t<N+I, std::tuple<T...>>...>>
sub(std::index_sequence<I...>);

在第一个示例中,只需将该类型包装在std::enable_if_t中即可看到该类型,没有其他要求。同样,仅声明就足够了,不需要定义。


编辑

如果您想使用自己的类模板而不是std::tuple,您可以轻松修改代码来实现:

#include<functional>
#include<tuple>
#include<cstddef>
#include<type_traits>

template<typename...>
struct bar {};

template<template<typename...> class C, std::size_t N, typename... T, std::size_t... I>
std::enable_if_t<(N < sizeof...(T)), C<std::tuple_element_t<N+I, std::tuple<T...>>...>>
sub(std::index_sequence<I...>);

template<template<typename...> class C, std::size_t N, typename... T>
using subpack = decltype(sub<C, N, T...>(std::make_index_sequence<sizeof...(T) - N>{}));

int main() {
    static_assert(std::is_same<subpack<bar, 2, int, float, char, double>, bar<char, double>>::value, "!");
}

编辑

根据添加到问题中的代码,上面的解决方案仍然有效。你只需要按照以下方式定义你的event类:

struct ev {};

template <typename>
struct event;

template <typename... T>
struct event<std::tuple<T...>>: ev {
    // ...
};

这样做时,当您执行以下操作:
event<param_pack<1, typename function_traits<F>::args>>

你仍然可以通过param_pack得到一个元组(在我的示例中使用了subpack using声明),但它与event的模板偏特化匹配,并且参数包作为T...可用。
这是你能做到的最好的方法,因为你不能将参数包放入using声明中。不管怎样,它只是起作用,所以可能可以解决你的问题。

类型为 std::tuple<T...>,有没有一种直接获取 pack 即 T... 的方法? - ssb
@subzero 我增加了另一个示例,以展示您如何使用自己的类模板。如果这对您有用,请告诉我。 - skypjack
@subzero function_traits::args 是你想要存储在一旁的类型列表吗? - skypjack
@subzero 你不能将它们放在using声明中。这是C++的工作方式,很抱歉。但是,如果你将它们存储在元组中,你可以通过std::tuple_element来解包,就像我在示例代码中所做的那样。这是你能做的最好的事情。 - skypjack
@subzero 答案已更新。请查看最后一节。我添加了一种解决问题的方法。这确实是你能做的最好的事情。 - skypjack
显示剩余3条评论

3
您可以这样做:
template <std::size_t N, typename ... Ts> struct drop;

template <typename ... Ts>
struct drop<0, Ts...>
{
    using type = std::tuple<Ts...>;
};

template <std::size_t N, typename T, typename ... Ts>
struct drop<N, T, Ts...>
{
    using type = typename drop<N - 1, Ts...>;
};

// Specialization to avoid the ambiguity
template <typename T, typename... Ts>
struct drop<0, T, Ts...>
{
    using type = std::tuple<T, Ts...>;
};

我尝试实现了类似的想法,但卡在编译器无法选择N = 0特化的事实上。您能否看一下:https://wandbox.org/permlink/u3QOECdxokexLgyG - Edgar Rokjān
@EdgarRokyan:确实,需要额外的专业知识。修复版 - Jarod42
完美,谢谢!不过看起来有点别扭啊 :( - Edgar Rokjān
类型为 std::tuple<T...>,有没有一种直接获取包 T... 的方法? - ssb
不要使用 using。但是你仍然可以很容易地将 std::tuple<Ts...> 更改为 bar<Ts...> - Jarod42

0

这是一个快速但不特别可重用的解决方案。

template <typename Pack, std::size_t N, std::size_t... Is>
void invoke_bar_impl(std::index_sequence<Is...>) {
    bar<std::tuple_element_t<N + Is, Pack>...>();
}

template <std::size_t N, typename... Ts>
void invoke_bar() {
    auto indices = std::make_index_sequence<sizeof...(Ts) - N>();
    invoke_bar_impl<std::tuple<Ts...>, N>(indices);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接