在C++11新标准中,可变模板参数可以算是一个非常重要也是非常强大的特性,相对于普通的模板,它提供了更高程度的泛化。
可变模板参数涉及2个方面:参数类型可变;参数个数可变。
1.可变模板参数的基本语法
首先回顾一下模板的基本使用方法,下图是模板函数和模板类的简单使用方式。
对于可变模板参数,个人理解是在原来模板的基础上增加了“...”关键字,当然,“...”位置不同,表示的含义也不同。
下面以一个可以打印任意个数任意类型(可以打印的类型)函数介绍“...”的使用方法
(1)模板参数包
第一个class...Types,写在template 的<>中,因此在使用模板函数时可以指定任意类型(在main函数中的Print调用)
(2)函数参数类型包(扩展包)
第二个Types...args,用在Print函数的形参中,表示任意类型的任意参数
(3)函数参数包
第三个args...,用在Print函数的递归调用中,实际上是实参
(4)其他情况
若想要获得参数包中的参数数量,需要使用sizeof...(args)来获得
PS:上面看到了...的使用情况:在形参左侧、参数右侧
1)省略号出现形参名字左侧,声明了一个参数包(parameter pack)。使用这个参数包,可以绑定0个或多个模板实参给这个可变模板形参参数包。参数包也可以用于非类型的模板参数(也就是参数数量不固定,但是每个参数的类型是一样的,即将Types args 替换成 int args)。
2)省略号出现包含参数包的表达式的右侧,则把这个参数包解开为一组实参(也就是args... = arg1, arg2, arg3,...)。
2. 可变模板参数的应用
(1)可变参数模板函数展开参数包
1)递归函数方式展开
上面是求和函数的递归调用,以递归的方式展开一般会将整个包拆解成第一个参数和其他所有参数,逐层拆解,最后需要具体化出一个边界条件。
上述代码是使用可变模板参数实现C语言中的printf()函数,采用了递归函数展开的方式。
2)逗号表达式展开
没有简单机制去在可变模板参数的每个单独值上迭代。几乎没有什么方式可以把参数包直接转为单独实参来使用。因此,想要将模板参数包拆分,需要借助print函数(该函数每次只能接受一个参数)
之前的帖子说:{(printarg(args), 0)...}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc... ),最终arr[]数组中存放了4个0,在我的试验下发现,可以写成
int arr[] = {(args)...};
最终arr[]数组中就可以存放1,2,3,4了。
(2)可变模版参数类展开参数包
1)可变参数模板用于递归继承
在c++11的STL中引入了一个新的容器tuple,下面代码将tuple的源码改造,展示tuple是如何通过递归继承来实现存放不同类型的数据的。
可以看出类的递归继承与函数的递归调用原理相似,都是将一组参数包分解成首个参数和其他所有参数,最终需要一个递归的结束条件。下图是某个tuple<>的继承关系图。
2)可变参数模板用于递归组合
在类中使用(复用)另外一个类,可以通过继承的方式,也可以采用组合的方式。同样韩式以Tuple为例,使用递归组合的方式来实现,代码如下。
下图是每个类之间的关系图,与递归继承类似,每个模板类都展开,不同的是类之间的关系是采用组合的方式。
3)模板递归和特化方式展开参数包
在类的递归继承中,模板参数的类型和数量都是可变的,下面的例子是针对类型相同、个数不同的情况,如何写代码。
3.总结
可变模板参数是C++11新标准中很有意思的特性,同样也具有很强大的功能。使用可变模版参数的关键是如何展开参数包,这一过程中包含了泛化、递归等思想。使用可变模板参数可以完成更加“万能”的函数、类,当然个人水平远达不到能够完全掌握该特性的程度,以后会在学习中补充。
参考:
侯捷老师的c++11新特性视频
https://blog.csdn.net/dolphin98629/article/details/95038039
https://blog.csdn.net/tony__lin/article/details/84677316