Variadic模板示例
Variadic Templates example
考虑下面的代码,我不明白为什么必须定义print的空函数。
#include <iostream>
using namespace std;
void print()
{
}
template <typename T, typename... Types>
void print (const T& firstArg, const Types&... args)
{
cout << firstArg << endl; // print first argument
print(args...); // call print() for remaining arguments
}
int main()
{
int i=1;
int j=2;
char c = 'T';
print(i,"hello",j,i,c,"word");
}
正确方式:
变分模板与induction
(一个数学概念)严格相关。
编译器解析以下函数调用
print('a', 3, 4.0f);
进入
std::cout<< 'a' <<std::endl;
print(3, 4.0f);
它被分解为
std::cout<< 'a' <<std::endl;
std::cout<< 3 <<std::endl;
print( 4.0f);
它被分解为
std::cout<< 'a' <<std::endl;
std::cout<< 3 <<std::endl;
std::cout<< 4.0f <<std::endl;
print();
在这一点上,它搜索匹配为空函数的函数重载。
- 所有具有1个或多个参数的函数都与可变参数模板匹配
- 所有没有参数的函数都与空函数匹配
罪魁祸首是,对于每个可能的参数组合,必须只有一个函数。
错误1:
执行以下操作将是一个错误
template< typename T>
void print( const T& arg) // first version
{
cout<< arg<<endl;
}
template <typename T, typename... Types>
void print (const T& firstArg, const Types&... args) // second version
{
cout << firstArg << endl; // print first argument
print(args...); // call print() for remaining arguments
}
因为当您调用print
时,编译器不知道该调用哪个函数。
print(3)
是指"第一个"版本还是"第二个"版本?两者都是有效的,因为第一个有1个参数,第二个也可以接受一个参数。
print(3); // error, ambiguous, which one you want to call, the 1st or the 2nd?
错误2:
以下无论如何都会是一个错误
// No empty function
template <typename T, typename... Types>
void print (const T& firstArg, const Types&... args)
{
cout << firstArg << endl; // print first argument
print(args...); // call print() for remaining arguments
}
事实上,如果你在没有编译器的情况下单独使用它,就会进行
print('k', 0, 6.5);
它被分解为
std::cout<<'k'<<std::endl;
print(0, 6.5);
它被分解为
std::cout<<'k'<<std::endl;
std::cout<< 0 <<std::endl;
print( 6.5);
它被分解为
std::cout<<'k'<<std::endl;
std::cout<< 0 <<std::endl;
std::cout<< 6.5 <<std::endl;
print(); //Oops error, no function 'print' to call with no arguments
正如您在上一次尝试中看到的那样,编译器尝试在没有参数的情况下调用print()
。然而,如果这样的函数不存在,它就不会被调用,这就是为什么你应该提供空函数的原因(别担心,编译器会优化代码,这样空函数就不会降低性能)。
如果没有空的print
函数,想象一个有两个参数的调用:
- print(a,b)=>cout<lt;a<lt;endl和call print(b)
- print(b)=>cout<lt;b<lt;endl并调用print()
oups,print()
不存在,因为只有至少有一个参数的print
存在!所以你需要一个没有参数的print
。
没有任何参数的print
是您的最终调用
因为(使用非常"简单"的解释级别)可变模板机制的工作方式类似于递归(它是NOT递推,但这是理解它的最简单方法),它"消耗"可变参数列表,因此您必须定义一个"stop"函数,当"consumped"参数列表为"空"时,它将在递归的最后一步中重复出现。这是我发现最容易理解这个相当复杂的概念的解释。
在第一步(在main
中),您将获得一个具有参数的函数:(int, const char*, int, int, char, const char*)
然后变差参数在变差函数本身中慢慢处理,让您在第二步中使用(const char*, int, int, char, const char*)
,然后使用(int, int, char, const char*)
,依此类推。。。直到你到达最后一个元素(const char*)
,当它在下一步中也被处理时,你会得到()
,编译器需要这个函数作为"终止符"
(是的,这是非常非技术性的,听起来像青蛙爷爷给小青蛙讲故事…)
递归是对可变模板进行编程的最常用方法,但它远不是唯一的方法。对于像这样的简单用例,直接在支持的初始值设定项列表中进行包扩展会更短,而且编译起来可能更快。
template <typename... Types>
void print (const Types&... args)
{
using expander = int[];
(void) expander { 0, (void(cout << args << endl), 0) ...};
}
在C++17中,我们将能够使用一个折叠表达式:
template <typename... Types>
void print (const Types&... args)
{
(void(cout << args << endl) , ...);
}
为了补充其他答案,我想展示编译器必须为模板调用生成什么。
nm -g -C ./a.out
(非优化构建)给出:
void print<char [5]>(char const (&) [5])
void print<char [5]>(char const (&) [5])
void print<char [6], int, int, char, char [5]>(char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<char [6], int, int, char, char [5]>(char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<char, char [5]>(char const&, char const (&) [5])
void print<char, char [5]>(char const&, char const (&) [5])
void print<int, char [6], int, int, char, char [5]>(int const&, char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<int, char, char [5]>(int const&, char const&, char const (&) [5])
void print<int, int, char, char [5]>(int const&, int const&, char const&, char const (&) [5])
void print<int, char [6], int, int, char, char [5]>(int const&, char const (&) [6], int const&, int const&, char const&, char const (&) [5])
void print<int, char, char [5]>(int const&, char const&, char const (&) [5])
void print<int, int, char, char [5]>(int const&, int const&, char const&, char const (&) [5])
print()
您可以看到print
函数的所有实例化。最终调用print()
的最后一个函数是void print<char [5]>(char const (&) [5])>
您可以看到,当传递一个空参数包时,模板参数列表必须为空。因此它只调用print()
。如果显式指定模板参数,如print<T, Args...>(t, args...)
,则会得到无限递归。
- Variadic模板未编译
- variadic模板中的模板参数推导失败
- [temp.variadic]中关于包扩展实例化的措辞
- std::绑定variadic模板和自动返回类型
- is_same和variadic模板编译时错误无效转换
- 限制variadic模板类中的构造函数访问
- 如何制作像类一样的"variadic"向量
- 用于"runtime variadic template"的重载逗号运算符
- variadic函数模板:基于n编译时值在运行时间自动n输入
- variadic函数和折叠表达:试图在Visual Studio 2017中编译时致命误差
- variadic模板代码中的GCC VS MSVC编译误差
- 使用variadic模板和运行时索引构造iterator_range
- variadic宏来创建结构
- 将variadic模板功能转换为许多具体功能
- 将variadic函数作为参数的函数
- variadic模板折叠程序在GCC9中失败
- 类型检测:使用variadic参数正确实现计算平均值的函数
- 如何在variadic模板参数中找到所有类的对象
- 在variadic函数中调用SNPRINTF和VSNPRINTF
- 如何访问variadic模板函数中的参数