Variadic模板和switch语句?

Tho*_*mas 19 c++ templates template-meta-programming variadic-templates c++14

我有以下函数,可以采用不同类型的N个参数,并以这种方式将它们转发到每个单独类型模板化的N个函数(带有两个参数的示例):

template <typename T1, typename T2>
bool func(int& counter, T1 x1, T2 x2) {
    switch (counter) {
        case 0:
            if (func2<T1>(x1)) {
                counter++;
                return true;
            } else {
                return false;
            }
        case 1:
            if (func2<T2>(x2)) {
                counter++;
                return true;
            } else {
                return false;
            }
        default:
            return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

我想用可变参数模板编写这个函数,以便它可以以类型安全的方式处理任意数量的参数.我可以看到一个使用递归函数的解决方案,传递计数器和可变参数索引并比较它们的相等性,但这似乎产生的效率远远低于上面的switch语句(if-checks序列与跳转表相比) ).

这可以使用模板元编程有效地完成,还是我需要为每个arity提供重载?

Nir*_*man 16

这是一个类似于max的解决方案,但它:a)清楚地将通用部分与特定于解决方案的部分分开,并且b)我表明clang完全优化了它.基本思想是在编译时从连续的整数序列构建一个switch case.我们这样做:

template <class T, T ... Is, class F>
auto compile_switch(T i, std::integer_sequence<T, Is...>, F f) {
  using return_type = std::common_type_t<decltype(f(std::integral_constant<T, Is>{}))...>;
  return_type ret;
  std::initializer_list<int> ({(i == Is ? (ret = f(std::integral_constant<T, Is>{})),0 : 0)...});
  return ret;
}
Run Code Online (Sandbox Code Playgroud)

我们的想法是整数作为一个整型常量类型传递给lambda,因此它可以在编译时上下文中使用.要将此与当前问题一起使用,我们所要做的就是将可变参数包转发为元组,并应用索引序列的常用技巧:

template <class T, std::size_t ... Is>
bool func_impl(std::size_t& counter, T&& t, std::index_sequence<Is...> is) {
  auto b = compile_switch(counter, is, [&] (auto i) -> bool {
    return func2(std::get<i>(std::move(t)));
  });
  if (b) ++counter;
  return b;
}

template <class ... Ts>
bool func(std::size_t & counter, Ts&& ... ts) {
  return func_impl(counter,
      std::forward_as_tuple(std::forward<Ts>(ts)...),
      std::index_sequence_for<Ts...>{});
}
Run Code Online (Sandbox Code Playgroud)

我们将使用这个定义func2来查看一些程序集:

template <class T>
bool func2(const T& t) { std::cerr << t; return std::is_trivial<T>::value; }
Run Code Online (Sandbox Code Playgroud)

看这里:https://godbolt.org/g/6idVPS我们注意到以下说明:

auto compile_switch<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul, bool func_impl<std::tuple<int&, double&, int&, unsigned long&, char const*&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul>(unsigned long&, std::tuple<int&, double&, int&, unsigned long&, char const*&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>&&, std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul>)::{lambda(auto:1)#1}>(unsigned long, std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul>, bool func_impl<std::tuple<int&, double&, int&, unsigned long&, char const*&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul>(unsigned long&, std::tuple<int&, double&, int&, unsigned long&, char const*&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>&&, std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul>)::{lambda(auto:1)#1}): # @auto compile_switch<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul, bool func_impl<std::tuple<int&, double&, int&, unsigned long&, char const*&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul>(unsigned long&, std::tuple<int&, double&, int&, unsigned long&, char const*&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>&&, std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul>)::{lambda(auto:1)#1}>(unsigned long, std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul>, bool func_impl<std::tuple<int&, double&, int&, unsigned long&, char const*&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul>(unsigned long&, std::tuple<int&, double&, int&, unsigned long&, char const*&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>&&, std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul>)::{lambda(auto:1)#1})
        push    r14
        push    rbx
        push    rax
        mov     bl, 1
        cmp     rdi, 5
        ja      .LBB2_11
        jmp     qword ptr [8*rdi + .LJTI2_0]
Run Code Online (Sandbox Code Playgroud)

俯视该标签,我们发现:

.LJTI2_0:
        .quad   .LBB2_2
        .quad   .LBB2_4
        .quad   .LBB2_5
        .quad   .LBB2_6
        .quad   .LBB2_7
        .quad   .LBB2_10
Run Code Online (Sandbox Code Playgroud)

换句话说,clang已将其转换为跳转表,并将所有调用内联func2.使用函数指针表是不可能的,正如一些人所建议的那样(至少我从来没有见过编译器这样做),事实上,通过switch case或者使用这种技术+ clang,获得汇编的唯一方法就是这样.可悲的是,gcc不会产生那么好的组装,但仍然不错.