扩展不同长度的参数包

问题描述 投票:0回答:4

我想“生成”函数指针的跳转表。所指向的函数有两种类型的模板。应该为两个类型列表中的每个可能的对实例化一个不同的函数。理想情况下,我们可以有这样的东西:

#include <tuple>

template <typename X, typename Y>
void foo()
{}

template <typename... Xs, typename... Ys>
void bar(const std::tuple<Xs...>&, const std::tuple<Ys...>&)
{
  using fun_ptr_type = void (*) (void);
  static constexpr fun_ptr_type jump_table[sizeof...(Xs) * sizeof...(Ys)]
    = {&foo<Xs, Ys>...};
}

int main ()
{
  using tuple0 = std::tuple<int, char, double>;
  using tuple1 = std::tuple<float, unsigned long>;

  bar(tuple0{}, tuple1{});
}

正如预期的那样,当元组长度不同时它会失败:

foo.cc:15:20: error: pack expansion contains parameter packs 'Xs' and 'Ys' that have different lengths (3 vs. 2)
    = {&foo<Xs, Ys>...};
            ~~  ~~ ^
foo.cc:23:3: note: in instantiation of function template specialization 'bar<int, char, double, float, unsigned long>' requested here
  bar(tuple0{}, tuple1{});
  ^
1 error generated.

为了实现这种功能,我已经尝试并成功使用了间接(第一个跳转表,其中包含指向另一个跳转表的函数的指针),但我发现它很笨拙。

所以,我的问题是:有解决方法吗?

c++ c++11 c++14 variadic-templates
4个回答
4
投票

您的示例代码是错误的,即使它可以编译(即当 sizeof...(Xs) == sizeof...(Ys) 时)。 假设你有 N 元元组,那么 Jump_table 有 N*N 个元素,但只有前 N 个元素是用函数 ptrs 初始化的。

首先,您需要内连接 2 个类型列表:

template<class A, class B>
struct Pair;

template<class... Ts>
struct List {};

template<class T, class... Ts>
using mul = List<Pair<T, Ts>...>;

template<class...>
struct cat;

template<class T>
struct cat<T>
{
    using type = T;
};

template<class... As, class... Bs>
struct cat<List<As...>, List<Bs...>>
{
    using type = List<As..., Bs...>;
};

template<class A, class B, class... Ts>
struct cat<A, B, Ts...>
{
    using type = typename cat<typename cat<A, B>::type, Ts...>::type;
};

template<class A, class B>
struct join;

template<class... As, class... Bs>
struct join<List<As...>, List<Bs...>>
{
    using type = typename cat<mul<As, Bs...>...>::type;
};

例如,

join<List<int[1], int[2]>, List<float[1], float[2], float[3]>>::type

给你

List<Pair<int[1], float[1]>, Pair<int[1], float[2]>, Pair<int[1], float[3]>, Pair<int[2], float[1]>, Pair<int[2], float[2]>, Pair<int[2], float[3]>

回到你的例子:

template <typename X, typename Y>
void foo()
{}

template<class T, std::size_t N>
struct jump_table
{
    template<class... As, class... Bs>
    constexpr jump_table(List<Pair<As, Bs>...>)
      : table{&foo<As, Bs>...}
    {}
    
    T table[N];
};

template <typename... Xs, typename... Ys>
void bar(const std::tuple<Xs...>&, const std::tuple<Ys...>&)
{
  using fun_ptr_type = void (*) (void);
  static constexpr jump_table<fun_ptr_type, sizeof...(Xs) * sizeof...(Ys)> table
    = {typename join<List<Xs...>, List<Ys...>>::type()};
}

int main ()
{
  using tuple0 = std::tuple<int, char, double>;
  using tuple1 = std::tuple<float, unsigned long>;

  bar(tuple0{}, tuple1{});
}

这应该符合您的预期。


4
投票

这里的其他答案对于当前的问题来说似乎太复杂了。我的做法如下:

#include <array>
#include <tuple>

template <typename X, typename Y> void foo() {}

using fun_ptr_type = void (*) (void);

// Build one level of the table.
template <typename X, typename ...Ys>
constexpr std::array<fun_ptr_type, sizeof...(Ys)>
  jump_table_inner = {{&foo<X, Ys>...}};

// Type doesn't matter, we're just declaring a primary template that we're
// about to partially specialize.
template <typename X, typename Y> void *jump_table;

// Build the complete table.
template <typename ...Xs, typename ...Ys>
constexpr std::array<std::array<fun_ptr_type, sizeof...(Ys)>, sizeof...(Xs)>
  jump_table<std::tuple<Xs...>, std::tuple<Ys...>> = {jump_table_inner<Xs, Ys...>...};

int main () {
  using tuple0 = std::tuple<int, char, double>;
  using tuple1 = std::tuple<float, unsigned long>;

  // Call function for (int, float).
  jump_table<tuple0, tuple1>[0][0]();
}

Clang 3.5 的 C++14 模式接受了这一点。


2
投票

我的产品扩展

context( f<Xs, Ys>... ) /* not what we want */
的正常解决方案是将其重写为
context2( g<Xs, Ys...>... )
。这意味着
g
负责对某些
Ys
扩展
X
,并且最终扩展对所有
g
执行
Xs
。这种重写的结果是我们引入了额外的嵌套,从而引入了不同的上下文。

在我们的例子中,我们将拥有一个函数指针数组的数组,而不是函数指针的平面数组。 与您尝试的解决方案不同,尽管这些实际上是我们关心的&foo<X, Y>

函数指针,并且扁平化很简单。

#include <cassert> #include <utility> #include <array> template<typename X, typename Y> void foo() {} using foo_type = void(*)(); template<typename... T> struct list { static constexpr auto size = sizeof...(T); }; template<typename X, typename Y, typename Indices = std::make_index_sequence<X::size * Y::size>> struct dispatch; template< template<typename...> class XList, typename... Xs , template<typename...> class YList, typename... Ys , std::size_t... Indices > struct dispatch<XList<Xs...>, YList<Ys...>, std::index_sequence<Indices...>> { private: static constexpr auto stride = sizeof...(Ys); using inner_type = std::array<foo_type, stride>; using multi_type = inner_type[sizeof...(Xs)]; template<typename X, typename... Yss> static constexpr inner_type inner() { return {{ &foo<X, Yss>... }}; } static constexpr multi_type multi_value = { inner<Xs, Ys...>()... }; public: static constexpr auto size = sizeof...(Xs) * sizeof...(Ys); static constexpr foo_type value[size] = { multi_value[Indices / stride][Indices % stride]... }; }; template< template<typename...> class XList, typename... Xs , template<typename...> class YList, typename... Ys , std::size_t... Indices > constexpr foo_type dispatch<XList<Xs...>, YList<Ys...>, std::index_sequence<Indices...>>::value[size]; int main() { using dispatch_t = dispatch< list<int, char, double>, list<float, unsigned long> >; constexpr auto&& table = dispatch_t::value; static_assert( dispatch_t::size == 6, "" ); static_assert( table[0] == &foo<int, float>, "" ); static_assert( table[1] == &foo<int, unsigned long>, "" ); static_assert( table[2] == &foo<char, float>, "" ); static_assert( table[3] == &foo<char, unsigned long>, "" ); static_assert( table[4] == &foo<double, float>, "" ); static_assert( table[5] == &foo<double, unsigned long>, "" ); }

Coliru 演示.


1
投票
您所拥有的实际上更像是两个列表的“zip”(

<X1,Y1>

<X2,Y2>
,...),当列表具有不同长度时,这不起作用。

要计算两者的“乘积”,我认为您必须使用辅助类才能使其工作。请参阅像您一样的其他问题:

如何创建类型列表的笛卡尔积?

© www.soinside.com 2019 - 2024. All rights reserved.