我有以下函数可以接受不同类型的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;
}
}
我想用可变参数模板编写这个函数,以便它可以以类型安全的方式处理任意数量的参数。我可以看到一个使用递归函数的解决方案,传递计数器和可变参数索引并比较它们是否相等,但这似乎产生的效率远低于上面的switch语句(if-checks序列与跳转表相比) )。
这可以使用模板元编程有效地完成,还是我需要为每个arity提供重载?
答案 0 :(得分:14)
这是一个类似于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;
}
这个想法是整数作为一个整型常量类型传递给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...>{});
}
我们将func2
的这个定义用于查看某个程序集:
template <class T>
bool func2(const T& t) { std::cerr << t; return std::is_trivial<T>::value; }
在此处查看: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]
向下看这个标签,我们发现:
.LJTI2_0:
.quad .LBB2_2
.quad .LBB2_4
.quad .LBB2_5
.quad .LBB2_6
.quad .LBB2_7
.quad .LBB2_10
换句话说,clang已将其转换为跳转表,和内联所有对func2
的调用。使用函数指针表是不可能的,正如一些人所建议的那样(至少我从来没有见过编译器这样做),事实上,通过switch case或者使用这种技术来获得组装的唯一方法就是这样。铛。可悲的是,gcc不会产生那么好的组装,但仍然不错。
答案 1 :(得分:3)
为了好玩,我建议采用以下方式
template <typename ... Ts>
bool func (int & cnt, Ts ... xs)
{
using unused = int[];
int i { -1 };
bool ret { true };
(void)unused { 0, ((++i == cnt ? (ret = func<Ts>(xs)) : true), 0)... };
if ( ret && (cnt <= i) )
++cnt;
return ret;
}
但我不认为这是切换方式的有效方式。
答案 2 :(得分:2)
此解决方案可能效率最高:
template<size_t I,class...Args>
bool func2_b(Args...arg)
{
if (func2(std::get<I>(std::tuple<Args...>{arg...})))
return true;
else
return false;
}
template<class...Args,size_t...Is>
bool func_(int& counter,std::index_sequence<Is...>,Args...args)
{
using ft = bool(*)(Args...);
ft table[]={func2_b<Is,Args...>...};
if (counter<0 || counter>=(int)sizeof...(Args))
return false;
return table[counter](args...);
}
template<class...Args>
bool func(int& counter,Args...xs)
{
return func_(counter,std::make_index_sequence<sizeof...(Args)>{},xs...);
}
答案 3 :(得分:1)
另外,为了好玩,这可能有点过于复杂
#include<type_traits>
#include<array>
template<typename T>
void g(T&& t)
{
// This function gets called
}
template<typename T>
void entry(void* p)
{
g(*(std::remove_reference_t<T>*)p);
}
template<size_t N>
using table_t = std::array<void (*)(void*), N>;
template<typename... Ts>
constexpr auto make_table()
{
return table_t<sizeof...(Ts)>{
entry<Ts>...
};
}
template<size_t N>
void f_(const table_t<N>&, int)
{
}
template<size_t N, typename T, typename... Ts>
void f_(const table_t<N>& table, int select, T&& t, Ts&&... ts)
{
if(select == N - sizeof...(Ts) - 1)
table[select]((void*)&t);
else
f_(table, select, std::forward<Ts>(ts)...);
}
template<typename... Ts>
void f(int select, Ts&&... ts)
{
static constexpr auto table = make_table<Ts...>();
if(select < 0 || select >= int(sizeof...(Ts)))
throw "out of bounds";
f_(table, select, std::forward<Ts>(ts)...);
}
在f
中滚动vtable并相应地发送到g
。
答案 4 :(得分:0)
理论上你可以自己做参数索引的二进制搜索:
#include <type_traits>
#include <tuple>
#include <typeinfo>
#include <iostream>
#include <algorithm>
template <std::size_t I>
using ic = std::integral_constant<std::size_t, I>;
template <class T>
bool func2(T) {
std::cout<<typeid(T).name()<<std::endl;
return true;
}
template <std::size_t N, class T>
bool func_impl(ic<0>, ic<N>, std::size_t &, T &&tup) {
constexpr int index = std::min(N - 1, std::tuple_size<T>{} - 1);
if (func2<std::tuple_element_t<index, std::decay_t<T>>>(std::get<index>(tup)))
return true;
return false;
}
template <std::size_t K, std::size_t N, class T>
bool func_impl(ic<K>, ic<N> n, std::size_t &counter, T &&tup) {
if (counter == N - 1) {
return func_impl(ic<0>{}, n, counter, std::forward<T>(tup));
}
if (counter < N) {
return func_impl(ic<K/2>{}, ic<N - K>{}, counter, std::forward<T>(tup));
} else {
return func_impl(ic<K/2>{}, ic<N + K>{}, counter, std::forward<T>(tup));
}
}
template <class... Ts>
bool func(std::size_t& counter, Ts&&... xs) {
return func_impl(ic<sizeof...(Ts)/2>{}, ic<sizeof...(Ts)/2>{}, counter, std::forward_as_tuple(xs...));
}
int main() {
std::size_t i = 0;
func<int, float, double, char>(i, 1, 2, 3, 4);
}