变长模板
变长模板
变长函数和变长模板参数
我们知道C++11已经支持了C99的变长宏,但是,无论是宏,还是变长参数(C语言中存在),整个机制的设计上, 没有任何一个对于传递参数的类型是了解的。我们可以看看变长函数的例子。
1 | double SumOfFloat(int count,...){ |
只有使用表达式va_arg(ap,double)
的时候,我们 才按照类型(实际是按类型长度)去变长参数列表中获得指定参数,以及为ap找到下一个参数的位置。对于printf函数来说,如何打印则得益于传递在字符串中的形如“%s”、“%d”这样的转义字,这都是对连续内存的一种解释,因此,对于一些没有定义转义字的非POD的数据来说,使用变长函数就会导致未定义的程序行为
1 | const char* msg = "hello%s"; |
对于c++这种强调类型的语言来说,是不愿意看到的,即使他是正确的。c++需要一种更现代的传参方式即类型和变量同时能够传递给变长参数的函数。一个好的方式就是使用C++的函数模板,在C++98 中,标准要求函数模板始终具有数目确定的模板参数及函数参数。
c++11中标准模板库存在 tuple
类,在C++11中,tuple是pair类的一种更为泛化的表现形式。比起 pair,tuple是可以接受任意多个不同类型的元素的集合。
1 | std::tuple<double,char,std::string> cloolctions |
因为tuple可以接受任意多的参数。此外,和pair类似地,我们也可以更为简单地使用C++11的模板函数make_tuple来创造一个tuple 模板类型。
1 | std::make_tuple(9.8,'g',"gravity"); |
在C++11中我们看到了所谓的变长模板(variadic template)的实现。
变长模板:模板参数包和函数参数包
变长模板的的语法,以tuple为例:
1 | template<typename ...Elements> class tuple; |
在c++11中,我们使用 … 来表示参数的变长的,Elements
被称为模板参数包,有了模板参数包,类模板tuple就可以接受多个参数做为模板参数,如:tuple<int , char , double>
,编译器则可以将多个模板参数打包成为“单个的”模板参数包 Elements,即Elements在进行模板推导的时候,就是一个包含int、char 和double三种类型类型集合。
与普通模板相似,模板参数包也可以是非类型的
1 | //如: |
一个模板参数包在模板推导时会被认为是模板的单个参数(虽然实际 上它将会打包任意数量的实参)。为了使用模板参数包,我们总是需 要将其解包(unpack)。在C++11中,这通常是通过一个名为包扩展 (pack expansion)的表达式来完成。比如:
1 | tempalte <typename ...A> class Template:private B <A...>{}; |
这里的...A
是表示课接受多个模板参数,参数包名为 A , A...
是一个包拓展,参数包会在包扩展的位置展开为多个参数
1 | template<typename T1,typename T2> class B{}; |
如何才能利用模板参数包及包扩展,使得模板能够接受任意多的模板参数,且均能实例化出有效的对象呢?
在C++11中,实现tuple模板的方式给出了一种使用模板参数包的答案。这个思路是使用数学的归纳法,转换为计算机能够实现的手段则是递归。通过定义递归的模板偏特化定义,我们可以使得模板参数包在实例化时能够层层展开,直到参数包中的参数逐渐耗尽或到达某个数量的边界为止。
1 | template<typename...Elements>class tuple; //变长模板的声明 |
我们声明了变长模板类tuple,其只包含一个模板参数,即Elements模板参数包。此外,我们又偏特化地定义了一个双参数的tuple的版本。该偏特化版本的tuple包含了两个参数,一个是类型模板参数Head,另一个则是模板参数包Tail,将Head型数据作为第一成员,而将使用了包扩展表达式的模板类tuple<Tail…>作为tuple<Head,Tail… >的私有基类。这样一来,当程序员实例化一个形如tuple< double,int,char,float>的类型时,则会引起基类的递归构造,这样的递归在tuple的参数包为0个的时候会结束。
使用非类型模板的例子:
1 | template<long...nums>struct Multiply; |
c++11中我们还可以声明变长的模板函数
1 | template<typename ...T> void f(T ...args); |
在C++11中, 标准要求函数参数包必须唯一,且是函数的最后一个参数(模板参数包没有这样的要求)。
有了模板参数包和函数参数包两个概念,我们就可以实现C中变长函数的功能了。我们可以看看这个C++11提案中实现新的printf的例子:
1 | void Printf(const char*s){ |
相比于变长函数(printf),变长函数模板(Printf)不会丢弃参数的类型信息。因此重载的cout的操作符 << 总是可以将具有类型的变量正确地打印出来。
变长模板进阶
c++11中可以展开参数包的位置:
表达式 初始化列表
基类描述列表 类成员初始化列表
模板参数列表 通用属性列表
lambda函数的捕获列表
一些有趣的包拓展语法:
1 | template <typename ...Arg> void func(Arg&&...){} |
类似的现象也会发生在函数模板上:
1 | template<typename...T>void DummyWrapper(T...t){} |
在C++11中,标 准还引入了新操作符sizeof...
其作用是计算参数包中的参数个数
1 | template<class...A> |
后面看不太懂了
参考:书籍《深入理解c++11》
C语言变长参数函数原理_code_peak的博客-CSDN博客