转载自: http://www.cnblogs.com/liyiwen/archive/2013/04/13/3018608.html

C++11 语言核心的改进中,最为关注的有 rvalue reference (这里有一篇拙作),lambda,variadic template。rvalue 规则稍微复杂,但一旦理解和记住了,应用上就没有什么困难。lambda 其实是一个“很自然”的语言设施,除了语法稍显诡异之外,习惯了就能马上用上,而且是能广泛用上的好东西。variadic template 这个新特性不像前两者,它本身的语法规则并不复杂,但是应用的时候确比较费脑子,好在这个技术主要是用在库的实现中,一个实现得好的库,并不会让我们对实现的细节作任何的要求。

variadic template 特性本身是一个很自然的需求,它完善了 C++ 的模板设计手段。原来的模板参数可以使类和函数的参数类型“任意化”,如果再加上“参数个数的任意化”,那么在参数方面的设计手段就基本上齐备了,有了variadic template 显然可以让设计出来的函数或是类有更大的复用性。因为有很多处理都是与“处理对象的个数”关系不大的,比如说打屏(printf),比如说比较大小(max,min),比如函数绑定子(bind,function要对应各种可能的函数就要能“任意”参数个数和类型)。如果不能对应任意个参数,那么就总会有人无法重用已有的实现,而不得不再重复地写一个自己需要的处理,而共通库的实现者为了尽可能地让自己写的类(函数)能复用在更多的场景,也不得不重复地写很多的代码或是用诡异的技巧,宏之类的去实现有限个“任意参数”的对应。(像TR1中的 bind 等)。

一、基本语法

声明一个带有可变参数个数的模板的语法如下所示:

  1. template<typename ...Element> class tuple;
  2. tuple<int, string> a;  // use it like this

在模板参数 Element 左边出现省略号 ... ,就是表示 Element 是一个模板参数包(template type parameter pack)。parameter pack(参数包)是新引入 C++ 中的概念,比如在这个例子中,Element 表示是一连串任意的参数打成的一个包。比如第2行中,Element 就是 int, string这个参数的合集。不仅“类型”的模板参数(也就是typename定义的参数)可以这样做,非类型的模板参数也可以这样做。比如下面这个例子:

  1. template<typename T, unsigned PrimaryDimesion, unsigned...Dimesions>
  2. class array { /**/ };
  3. array<double, 3, 3> rotation_matrix; //3x3 ratiation matrix

现在我们知道parameter pack了,怎么在程序中真正具体地去处理打包进来的“任意个数”的参数呢?我原来以为,编译器会提供一些像get_param<1>(Element) 之类的内建的“参数抽取函数”给程序员使用结果不是!!看来我的思路还是太“过程式了”。其实 C++11 用的是 unpack 和类似函数重载似的“模板特化”来抽取参数的。这是应用 variadic tempate 最“坑爹”的部分,因为它要求对“递归”和“人肉代码展开”有一定的功力啊。还是看例子吧:

  1. template<typename... Elements> class tuple;
  2. template<typename Head, typename... Tail>
  3. class tuple<Head, Tail...> : private tuple<Tail...> {
  4. Head head;
  5. public:
  6. /* implementation */
  7. };
  8. template<>
  9. class tuple<> {
  10. /* zero-tuple implementation */
  11. };

第1行声明了一个可以对应任意参数的tuple类,第2行到7行声明了这个类的一个部分特化,注意,这就是抽取参数的典型方法了。给个图还是最方便用来理解的:

(如果看不全,点击它可以看全图)

好吧,我觉得这个图是本篇博客中最有价值的部分了,转载一定要注明出处啊!

只说明一下针对 parameter pack 相对的另一个概念,模板参数后面带省略号 ... 就是一个解包(unpack),会把这个参数所表示的参数列表解开后去匹配新的模板,或是进行模板展开。其它的,我觉得有上面这个图,实在不需要再多费什么笔墨去讲这一部分了。

二、例子

新的标准库里,有很多个库都直接依赖于 variadic template 这个语言特性,比如,tuple,bind,function。但他们比较复杂,也不够“惊艳”。C++ 老爸的 C++11 的 FQA 和 Wikipedia 的例子都是“类型安全”的printf:

  1. void printf(const char *s)
  2. {
  3. while (*s) {
  4. if (*s == '%') {
  5. if (*(s + 1) == '%') {
  6. ++s;
  7. }
  8. else {
  9. throw std::runtime_error("invalid format string: missing arguments");
  10. }
  11. }
  12. std::cout << *s++;
  13. }
  14. }
  15. template<typename T, typename... Args>
  16. void printf(const char *s, T value, Args... args)
  17. {
  18. while (*s) {
  19. if (*s == '%') {
  20. if (*(s + 1) == '%') {
  21. ++s;
  22. }
  23. else {
  24. std::cout << value;
  25. // call even when *s == 0 to detect extra arguments
  26. printf(s + 1, args...);
  27. return;
  28. }
  29. }
  30. std::cout << *s++;
  31. }
  32. throw std::logic_error("extra arguments provided to printf");
  33. }

这个例子确实很棒,够短。仅仅X行的代码中,就用到了选择(if)和循环(while),递归(printf自身的递归),重载(cout的<<对种输出类型的重载),异常。这个例子不细讲,大家试着用“上面的图”来试试分析一下吧。(这个例子用来面试,让面试者来解析下是不是不错?……)

三、杂想

这里有篇文章有一些关于这个特性加入 C++ 的动机,简而言之,像 function ,bind 之类如果没有这个特性,实现起来会非常的麻烦,而且这样实现出来的代码也会使得编译时间变得很长。

你看,如果要支持39个参数,编译时间会到700秒。而使用 variadic template 之后,代码小了 40 K,同时各种参数个数的情况下编译时间都小于 1 秒。

这很好。但是,这个特性用起来真的不是那么容易,你也看到了。在和其它的特性一起使用的时候(多重继承,右值引用)等在一起的时候,会更加地“复杂”。但它是一个利器,用得好,会造出非常好用的库。否则,我们就用库好了,不要为去学习,记住和非要在日常编程中使用它而烦恼了。

四、参考资料

  1. Variadic Templates for GCC:http://www.generic-programming.org/~dgregor/cpp/variadic-templates.html
  2. 维基百科:http://en.wikipedia.org/wiki/Variadic_template
  3. C++11当时的提案:N2080 (这个特别好,或是最好,我所见过的)

C++ 变长模板参数的更多相关文章

  1. C++11变长参数模板

    [C++11变长参数模板] C++03只有固定模板参数.C++11 加入新的表示法,允许任意个数.任意类别的模板参数,不必在定义时将参数的个数固定. 实参的个数也可以是 0,所以 tuple<& ...

  2. C++中的变长参数

    新参与的项目中,为了使用共享内存和自定义内存池,我们自己定义了MemNew函数,且在函数内部对于非pod类型自动执行构造函数.在需要的地方调用自定义的MemNew函数.这样就带来一个问题,使用stl的 ...

  3. C++17尝鲜:变长 using 声明

    using 声明 先来看 using 声明在类中的应用: 代码1 #include <iostream> using namespace std; struct A { void f(in ...

  4. C++11的模板新特性-变长参数的模板

    这个特性很赞,直接给例子吧,假如我要设计一个类,CachedFetcher内部可能使用std::map也可能使用std::unordered_map,也可能是其它的map,怎么设计呢?没有C++11变 ...

  5. 介绍C++11标准的变长参数模板

    目前大部分主流编译器的最新版本均支持了C++11标准(官方名为ISO/IEC14882:2011)大部分的语法特性,其中比较难理解的新语法特性可能要属变长参数模板(variadic template) ...

  6. C++雾中风景9:emplace_back与可变长模板

    C++11的版本在vector容器添加了emplace_back方法,相对于原先的push_back方法能够在一定程度上提升vector容器的表现性能.所以我们从STL源码角度来切入,看看这两种方法有 ...

  7. C99新增内容之变长数组(VLA)

    我们在使用多维数组是有一点,任何情况下只能省略第一维的长度.比如在函数中要传一个数组时,数组的行可以在函数调用时传递,当属数组的列却只能在能被预置在函数内部.看下面一个例子: #define COLS ...

  8. c++11变长参数函数模板

    By francis_hao    Mar 25,2018   一个最简单的实例大概是这个样子: #include <iostream>using namespace std; /*变长参 ...

  9. 【Unix环境高级编程】编写变长参数函数

    文件的格式输入输出函数都支持变长参数.定义时,变长参数列表通过省略号'...'表示, 因此函数定义格式为: type 函数名(parm1, parm2,parmN,...); Unix的变长参数通过v ...

随机推荐

  1. 001_ jQuery的表格插件dataTable详解

    一. 1.启用id为"datatable1"标签的html的表格jQuery库 $("#datatable1").dataTable( ) Reference: ...

  2. python之os

    os 系统级别的操作 os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径 os.chdir("dirname") 改变当前脚本工作目录:相当于shell ...

  3. 在 .NET Core 中结合 HttpClientFactory 使用 Polly(中篇)

    译者:王亮作者:Polly 团队原文:http://t.cn/EhZ90oq声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的(包括标题).其中可能会去除一些不影响理解但本人实在不知道如 ...

  4. es6可变参数-扩展运算符

    es5中参数不确定个数的情况下: //求参数和 function f(){ var a = Array.prototype.slice.call(arguments); var sum = 0; a. ...

  5. springdata 动态查询 是用来查询的 仅提供查询功能

    springdata 动态查询 是用来查询的 仅提供查询功能

  6. 初识服务器和Linux

    一.什么是计算机 1.介绍 一说到计算机,我们首先想到的就是电脑,没错,电脑就是计算机,但是计算机不只是电脑. 所谓的电脑就是一种计算机,而计算机其实是:接收使用者输入的指令与资料,经中央处理器的数学 ...

  7. Magento2 Service contracts 服务合同

    服务合同 Magento是一个模块化系统,它使第三方开发人员能够定制和覆盖其框架的核心部分.然而,这种灵活性是有代价的. 业务逻辑倾向于泄漏Magento系统的各个层,这表现为重复且不一致的代码. 商 ...

  8. 51nod 1318 最大公约数与最小公倍数方程组(2-SAT)

    题意 给你 \(n\) 个元素,\(m\) 个方程. 每个方程形如 \[ \begin{align} \gcd(x_i, y_i)=c_i\\ \mathrm{lcm}(x_i,y_i) = d_i ...

  9. 用IntelliJ IDEA 开发Spring+SpringMVC+Mybatis框架 分步搭建二:配置MyBatis 并测试(1 构建目录环境和依赖)

    引言:在用IntelliJ IDEA 开发Spring+SpringMVC+Mybatis框架 分步搭建一   的基础上 继续进行项目搭建 该部分的主要目的是测通MyBatis 及Spring-dao ...

  10. sublime text 批量删除空白行

    CTRL+H打开replace功能,勾选上左侧的regular expression,并填写 find what栏 : \s+$  (正则表达式)replace with栏 : (这行留空) 接着点r ...