【问题标题】:Expand parameter packs with different lengths展开不同长度的参数包
【发布时间】:2014-09-04 03:47:32
【问题描述】:

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

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

为了实现这种功能,我已经尝试了indirection(第一个跳转表,其中包含指向另一个跳转表的函数的指针),但我觉得它很笨拙。

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

【问题讨论】:

    标签: c++ c++11 c++14 variadic-templates


    【解决方案1】:

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

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

    template<class A, class B>
    struct P;
    
    template<class... Ts>
    struct L {};
    
    template<class T, class... Ts>
    using mul = L<P<T, Ts>...>;
    
    template<class...>
    struct cat;
    
    template<class T>
    struct cat<T>
    {
        using type = T;
    };
    
    template<class... As, class... Bs>
    struct cat<L<As...>, L<Bs...>>
    {
        using type = L<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<L<As...>, L<Bs...>>
    {
        using type = typename cat<mul<As, Bs...>...>::type;
    };
    

    例如,

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

    给你

    L<P<int[1], float[1]>, P<int[1], float[2]>, P<int[1], float[3]>, P<int[2], float[1]>, P<int[2], float[2]>, P<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(L<P<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<L<Xs...>, L<Ys...>>::type()};
    }
    
    int main ()
    {
      using tuple0 = std::tuple<int, char, double>;
      using tuple1 = std::tuple<float, unsigned long>;
    
      bar(tuple0{}, tuple1{});
    }
    

    这应该符合您的预期。

    【讨论】:

    • 谢谢,非常简单,列表操作部分很容易重复使用。
    • 使用template&lt;class...&gt;struct L{using type=L;}; 和继承以及一些template&lt;class...Ts&gt;using cat_t=typename cat&lt;Ts...&gt;::type; 别名可以使列表内容更加流畅。 See here.
    【解决方案2】:

    对于手头的问题,这里的其他答案似乎太复杂了。这是我的做法:

    #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 模式下接受了这一点。

    【讨论】:

    • 我在问这个问题之前尝试了类似的方法,但它不能用 clang 3.4 或 g++ 4.9 编译...但是,我同意,这是一个更简单的解决方案。
    • 我反对你将我的回答描述为“太复杂”,因为我们正在做同样的事情。我更详细地通过类模板专业化来做这件事,这是为了教学目的——我努力让其他人可以在 Coliru 上尝试代码,这样即使我们的编译器不同,他们也能理解我的答案。显然,主要由代码组成的简短回答也有其优点,但我认为这种副手评论不合适。
    • 我不反对您的解决方案具有更多的教育价值;它暴露了更多的技术并在更多的情况下工作。但这正是导致手头问题过于复杂的原因。
    【解决方案3】:

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

    在我们的例子中,我们将有一个函数指针数组,而不是一个平面的函数指针数组。 您尝试的解决方案不同,尽管这些确实是我们关心的 &amp;foo&lt;X, Y&gt; 函数指针,而且展平很简单。

    #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 demo.

    【讨论】:

    • 感谢您的回答。但是,我很难理解main() 之前的声明。它看起来像一个显式实例化,因为如果我删除它,编译器会抱怨相应的缺失符号。另外,我不明白编译器如何在value[size] 中看到dispatchsize
    • @AlexandreHamez 不是实例化,而是定义。 This is what it looks like 用于非模板类。因为dispatch 是一个类模板,所以我们必须在我们的例子中添加一些样板。从来不需要这样的定义(对于constexpr 静态数据成员),但编译器仍然可以要求它。例如,我的不要求它。
    【解决方案4】:

    您所拥有的实际上更像是两个列表(&lt;X1,Y1&gt;&lt;X2,Y2&gt;、...)的“压缩”,当列表长度不同时,它不起作用。

    要计算两者的“乘积”,我认为您必须使用辅助类才能使其工作。看到像你这样的另一个问题:How to create the Cartesian product of a type list?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-03-04
      • 2017-06-22
      • 1970-01-01
      • 1970-01-01
      • 2014-01-15
      • 2013-09-19
      • 1970-01-01
      相关资源
      最近更新 更多