1.可变模板参数
既是支持可变数量参数的函数模板和类模板
即可分为两种参数包:模板参数包(0~多个模板参数的集合)和函数参数包(0~多个模板参数的集合)。
//函数模板中的右值引用参数都叫万能引用
template<class ...Args>//...不可省略且要注意...在不同位置处所放的位置
void Print(Args&&... args)
{
cout << sizeof...(args) << endl;
//sizeof...可以视为一个新的关键字,用于计算参数包的大小
}
可以将参数包视为模板的模板,既是用于生成不同模板参数个数的模板
cpp
template<class ...Args>
void Print(Args&&... args)
{
//...
}
int main()
{
Print(1,2.3);
Print(1,1);
Print(1);
return 0;
}
//有Print实际实参模板生成的过程
//先是参数包根据实参个数生成不同模板参数个数的模板
//template<class T1,class T2>
//template<class T1>
//再根据实参类型生成对应模板类
//template<int,double>
//template<int,int>
//template<int>
(对左右引用的再理解,使用万能引用的情况在于使用左右引用时的函数体内容就只有参数的引用类型不同,当在分别使用左右引用时的内部代码内容需求不同时还是要写成两个函数的)
总结一下就是函数模板是用于实例化出不同参数类型的函数而可变参数模板就是实例化出不同参数个数的函数模板(类模板也是同理)。
2.包扩展
包展开的逻辑是在编译时运行的,即包模板的展开结束条件是不能用return来结束,而是要单独开一个空参的同名函数作为结束条件。
直白的说就是每一个参数包中的一个参数的提取要通过编译时递归一个一个取。
编译时递归的每一次递归都是生成一个新函数,每一次递归的函数之间构成函数重载,而参数包空了时不会生成对应的无参函数因此需要我们自己写这个无参函数作为结束条件。
总结就是参数包的遍历只能通过编译性递归来实现,如何递归全由编译器来实现,我们只需写中止条件即可。
...的总结:
cpp
Args... args//类型后面的...代表这是一个参数包
args... //变量后面的...代表展开这个参数包(没有...时默认只传一个参数模板)
func(args)... //每次传参数包中的一个参数,然后...代表不断展开参数包直到参数包为空
template < class T,class T1>
const T & GetArg(const T & x,const T1& v)
{
cout << x << " " << v << ' ';
return x;
}
//这里的参数包不一定和Print的参数包相同
//只与GetArg总返回类型的参数包有关
template < class ...Args>
void Arguments(Args... args)
{}
template < class ...Args>
void Print(Args... args)
{
//这里传两个,可以说明args只能一个一个传
Arguments(GetArg(args,args)...);
}
3.emplace系列与push系列(以list来比较)


可以发现emplace的参数是万能引用,push是左右值的函数重载。在插入左右值时二者都是调用的拷贝构造/移动构造。
区别一:emplace作为可变模板参数,在插入值前还没有实例化,而push在插入值前已经实例化了。以往list中插入string为例:插入"1111111"时,emplace实例化为const char*直接接收该字符串然后调用const char*形参的构造函数,而push已经实例化为string,因此其会先调用const char*的构造函数,然后再调用string的拷贝构造。因此push会比emplace多走一次构造。
({1,2}这种多参的隐式类型转换的传参前提是有对应类型的构造函数,(未实例化的构造函数是不行的,因此参数包也不行))
区别二:多参构造时传参形式也不同,emplace由于参数包的原因可以直接传多个参调构造,而push要用{}括起来调隐式类型后转换为拷贝构造。
emplace(1,1,1,1);
push({1,1,1,1});
总结:emplace系列兼容push系列和insert的功能,部分场景下,emplace支持调直接构造而push还要再调一层移动/拷贝构造。
emplace的底层实现也很简单,每一个底层调用的函数都加一个参数包为形参的构造函数即可.
cpp
template<class... Args>
void emplace_back(Args&&... args)//直接全部展开
{insert(end(),std::forward<Args>(args)...);}
template<class... Args>
bool insert(Args&&... args)
{Node* newnode = new Node(std::forward<Args>(args)...);}
template<class... Args>
struct list_node
{
//如果list_node有与参数包类型匹配的构造函数,那么还要往下传直到匹配为止
//但如果list_node就有与参数包类型匹配的构造函数就直接调用了
template<class... Args>
list_node(Args&&... args)
{}
}
最后说明一些万能引用的缺点:
1.万能引用无法接受{}的隐式类型转换的传参
2.当万能引用与参数包而这作为函数重载同时出现时,当只传一个参数时,编译器会不知道传给那个二报错。
4.lambda
是一个匿名函数对象,可以定义在函数内部。
使用:
cpp
//[]是捕捉,()是形参->type是返回类型,{}是函数体
auto add = [](int x,int y)->int{return x + y;}
//add可用于直接调用
add(1,2);