文章目录

  • 前言
  • 可变参数模板的定义
  • 参数包的展开
    • 递归函数方式展开
    • 逗号表达式展开
    • enable_if方式展开
    • 折叠表达式展开(c++17)
  • 总结

前言

可变参数模板(variadic templates)是C++11新增的强大的特性之一,它对模板参数进行了高度泛化,能表示0到任意个数、任意类型的参数。相比C++98/03这些类模版和函数模版中只能含固定数量模版参数的“老古董”,可变模版参数无疑是一个巨大的进步。

如果是刚接触可变参数模板可能会觉得比较抽象,使用起来会不太顺手,使用可变参数模板时通常离不开模板参数的展开,所以本文来列举一些常用的模板展开方式,帮助我们来对可变参数模板有一个初步的了解。

可变参数模板的定义

可变参数模板和普通模板的定义类似,在写法上需要在 typenameclass 后面带上省略号...,以下为一个常见的可变参数函数模板:

template <class... T>
void func(T... args)
{//...
}

上面这个函数模板的参数 args 前面有省略号,所以它就是一个被称为模板参数包(template parameter pack)的可变模版参数,它里面包含了0到N个模版参数,而我们是无法直接获取 args 中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这也是本文要重点总结的内容。

参数包的展开

参数包展开的方式随着c++语言的发展也在与时俱进,我们以实现一个可变参格式化打印函数为例,列举一些常用的方式:

递归函数方式展开

#include <iostream>void FormatPrint()
{std::cout << std::endl;
}template <class T, class ...Args>
void FormatPrint(T first, Args... args)
{std::cout << "[" << first << "]";FormatPrint(args...);
}int main(void)
{FormatPrint(1, 2, 3, 4);FormatPrint("good", 2, "hello", 4, 110);return 0;
}

这种递归展开的方式与递归函数的定义是一样的,需要递归出口和不断调用自身,仔细看看这个函数模板是不是都满足啦?递归出口就是这个无模板参数的 FormatPrint,并且在有参模板中一直在调用自身,递归调用的过程时这样的 FormatPrint(4,3,2,1) -> FormatPrint(3,2,1) -> FormatPrint(2,1) -> FormatPrint(1) -> FormatPrint(),输出内容如下:

>albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testtemplate.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
[1][2][3][4]
[good][2][hello][4][110]

逗号表达式展开

#include <iostream>template <class ...Args>
void FormatPrint(Args... args)
{(void)std::initializer_list<int>{ (std::cout << "[" << args << "]", 0)... };std::cout << std::endl;
}int main(void)
{FormatPrint(1, 2, 3, 4);FormatPrint("good", 2, "hello", 4, 110);return 0;
}

这种方式用到了C++11的新特性初始化列表(Initializer lists)以及很传统的逗号表达式,我们知道逗号表达式的优先级最低,(a, b) 这个表达式的值就是 b,那么上述代码中(std::cout << "[" << args << "]", 0)这个表达式的值就是0,初始化列表保证其中的内容从左往右执行,args参数包会被逐步展开,表达式前的(void)是为了防止变量未使用的警告,运行过后我们就得到了一个N个元素为0的初始化列表,内容也被格式化输出了:

albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testtemplate.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
[1][2][3][4]
[good][2][hello][4][110]

说到这顺便提一下,可以使用sizeof...(args)得到参数包中参数个数。

enable_if方式展开

#include <iostream>
#include <tuple>
#include <type_traits>template<std::size_t k = 0, typename tup>
typename std::enable_if<k == std::tuple_size<tup>::value>::type FormatTuple(const tup& t)
{std::cout << std::endl;
}template<std::size_t k = 0, typename tup>
typename std::enable_if<k < std::tuple_size<tup>::value>::type FormatTuple(const tup& t){std::cout << "[" << std::get<k>(t) << "]";FormatTuple<k + 1>(t);
}template<typename... Args>
void FormatPrint(Args... args)
{FormatTuple(std::make_tuple(args...));
}int main(void)
{FormatPrint(1, 2, 3, 4);FormatPrint("good", 2, "hello", 4, 110);return 0;
}

C++11的enable_if常用于构建需要根据不同的类型的条件实例化不同模板的时候。顾名思义,当满足条件时类型有效。可作为选择类型的小工具,其广泛的应用在 C++ 的模板元编程(meta programming)之中,利用的就是SFINAE原则,英文全称为Substitution failure is not an error,意思就是匹配失败不是错误,假如有一个特化会导致编译时错误,只要还有别的选择,那么就无视这个特化错误而去选择另外的实现,这里的特化概念不再展开,感兴趣可以自行了解,后续可以单独总结一下。

在上面的代码实现中,基本思路是先将可变模版参数转换为std::tuple,然后通过递增参数的索引来选择恰当的FormatTuple函数,当参数的索引小于tuple元素个数时,会不断取出当前索引位置的参数并输出,当参数索引等于总的参数个数时调用另一个模板重载函数终止递归,编译运行输入以下内容:

albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testtemplate.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
[1][2][3][4]
[good][2][hello][4][110]

折叠表达式展开(c++17)

#include <iostream>template<typename... Args>
void FormatPrint(Args... args)
{(std::cout << ... << args) << std::endl;
}int main(void)
{FormatPrint(1, 2, 3, 4);FormatPrint("good", 2, "hello", 4, 110);return 0;
}

折叠表达式(Fold Expressions)是C++17新引进的语法特性,使用折叠表达式可以简化对C++11中引入的参数包的处理,可以在某些情况下避免使用递归,更加方便的展开参数,如上述代码中展示的这样可以方便的展开参数包,不过输出的内容和之前的有些不一样:

albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testtemplate.cpp --std=c++17
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
1234
good2hello4110

对比结果发现缺少了格式化的信息,需要以辅助函数的方式来格式化:

#include <iostream>template<typename T>
string format(T t) {std::stringstream ss;ss << "[" << t << "]";return ss.str();
}template<typename... Args>
void FormatPrint(Args... args)
{(std::cout << ... << format(args)) << std::endl;
}int main(void)
{FormatPrint(1, 2, 3, 4);FormatPrint("good", 2, "hello", 4, 110);return 0;
}

这次格式化内容就被加进来了:

albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testtemplate.cpp --std=c++17
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
[1][2][3][4]
[good][2][hello][4][110]

这样好像还是有点麻烦,我们可以把折叠表达式和逗号表达式组合使用,这样得到的代码就简单多啦,也能完成格式化输出的任务:

#include <iostream>template<typename... Args>
void FormatPrint(Args... args)
{(std::cout << ... << (std::cout << "[" << args, "]")) << std::endl;
}int main(void)
{FormatPrint(1, 2, 3, 4);FormatPrint("good", 2, "hello", 4, 110);return 0;
}

总结

  • Variadic templates 是C++11新增的强大的特性之一,它对模板参数进行了高度泛化
  • Initializer lists 是C++11新加的特性,可以作为函数参数和返回值,长度不受限制比较方便
  • Fold Expressions 是C++17新引进的语法特性,可以方便的展开可变参数模板的参数包
  • 可变参数模板的参数包在C++11的环境下,可以利用递归、逗号表达式、enable_if等方式进行展开

==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==


有些人苦中作乐,而有些人却是身在福中不知福。人性本贪婪,只是度不同。我虽知福,奈何要想一家安稳还差的太多~

C++可变参数模板的展开方式相关推荐

  1. C++之initializer_list,可变参数模板参数展开方法

    initializer_list介绍 模板initializer_list是C++11新增的,可以使用初始化列表语法将STL容器初始化为一系列值,在使用 { }来进行初始化的时候,其实是调用了将 in ...

  2. C++11 -------- 类的新功能+可变参数模板+emplace接口

    目录 类的新功能 1.默认成员函数 (1)八个默认成员函数 (2)默认移动构造和移动赋值的生成条件 (3)默认生成的移动构造和移动赋值会做什么 (4)验证默认生成的移动构造和移动赋值所做的工作 2.类 ...

  3. 可变参数模板、右值引用带来的移动语义完美转发、lambda表达式的理解

    可变参数模板 可变参数模板对参数进行了高度泛化,可以表示任意数目.任意类型的参数: 语法为:在class或者typename后面带上省略号. Template<class ... T> v ...

  4. 【C++】C++11新特性——可变参数模板|function|bind

    文章目录 一.可变参数模板 1.1 可变参数的函数模板 1.2 递归函数方式展开参数包 1.3 逗号表达式展开参数包 1.4 empalce相关接口函数 二.包装器function 2.1 funct ...

  5. C++_可变参数模板到emplace_back再到construct再到forward

    C++_可变参数模板到emplace_back再到construct再到forward 1.可变参数模板 具体定义如下图所示: 编写一个可变参数版本: 1.1sizeof-运算符 2.emplace_ ...

  6. 可变参数模板(参考《C++ Templates 英文版第二版》)

    可变参数模板(参考<C++ Templates 英文版第二版>) Chapter 4 可变参数模板 自从C++11,模板可以接受可变数量的参数 4.1 可变参数模板 可以定义模板,去接受无 ...

  7. C++ Variadic Templates(可变参数模板)

    本文参考侯捷老师的视频:https://www.youtube.com/watch?v=TJIb9TGfDIw&list=PL-X74YXt4LVYo_bk-jHMV5T3LHRYRbZoH ...

  8. C++ 0x 使用可变参数模板类 实现 C# 的委托机制

    1 #ifndef _ZTC_DELEGATE_H_ 2 #define _ZTC_DELEGATE_H_ 3 4 #include <vector> 5 #include <fun ...

  9. 实现对手机联系人列表进行读写操作,并用RecyclerView收缩展开方式展现

    实现对手机联系人列表进行读写操作,并用RecyclerView收缩展开方式展现 在之前做的类微信界面上加了显示手机联系人,姓名,电话,邮箱三项信息的功能,同时可以添加联系人同步到手机联系人记录中,添加 ...

最新文章

  1. ES6转ES5:Gulp+Babel
  2. mac下用scp命令实现本地文件与服务器Linux文件之间的相互传输
  3. 达拉斯大学计算机硕士专业排名,美国大学研究生专业排名:人机交互
  4. 6 | Spatial-based GNN/convolution模型之MoNET
  5. vb红绿灯自动切换_vb教程之用VB编写“红绿灯”程序
  6. office2016+visio2016
  7. 谷歌地球到底有多厉害?附查看高清卫星影像方法
  8. 目前最新android处理器排行榜,手机处理器最新排行榜天梯图_现在安卓手机的处理器哪个比较好...
  9. 台式计算机显示不了无线网络,我是台式电脑,插上无线网卡怎么我的链接里不显示无线...
  10. Hibernate使用手册(官网)
  11. C# 压缩和修复Access数据库
  12. 前端性能优化之----静态文件客户端离线缓存_20191110
  13. 【C语言】强符号和弱符号
  14. canvas-实现放大镜效果
  15. H5 语音合成播报功能
  16. 童鞋们有福了,U880 GPS使用指南终于找到了,不看后悔
  17. 升级 Elasticsearch
  18. Silverlight Spy初探
  19. 空洞卷积dilated conv
  20. go ip过滤_「净网2020」!利用GOIP设备协助作案上百起的多名“帮凶”被抓!

热门文章

  1. 寻找一处属于自己的角落?
  2. python动态规划经典题目_矿工问题—动态规划经典题目
  3. 【论文总结】《Neural Reading Comprehension and Beyond(2018,第一部分)》(阅读理解任务综述)
  4. 微软暗中运作企图影响我国政府软件采购
  5. 多克创新祝大家在新的一年里阖家欢乐,身体健康,万事如意。值此新春佳节! 恭祝: 新春快乐! 兔年吉祥!
  6. PTA 列车调度 python
  7. mysql笛卡尔积的过程
  8. 黑马优购小程序之接口优化
  9. Vue代理请求数据出现404
  10. android GPS 获取城市信息