《C++ Primer Plus》第18章:探讨 C++ 新标准(6)
可变参数模板
可变参数模板(variadic template)让您能够创建这样的模板函数和模板类,即可接收可变数量的参数。这里介绍可变参数模板函数。例如,假设要编写一个函数,它可接受任意数量的参数,参数的类型只需是 cout 能够显示的即可,并将参数显示为用逗号分隔的列表。请看下面的代码:
int n = 14;
double x = 2.71828;
std::string mr = "Mr. String objects!";
show_list(n, x);
show_list(x*x, '!', 7, mr);
这里的目标是,定义 show_list(),让上述代码能够通过编译并生成如下输出:
14, 2.71828
7.3805, !, 7, Mr. String objects!
要创建可变参数模板,需要理解几个要点:
- 模板参数包(parameter pack);
- 函数参数包;
- 展开(unpack)参数包;
- 递归。
模板和函数参数包
为理解参数包的工作原理,首先来看一个简单的模板函数,它显示一个只有一项的列表:
template<typename T>
void show_list0(T value) {std::cout << value << ", ";
}
在上述定义中,有两个参数列表。模板参数列表只包含T,而函数参数列表只包含 value。下面的函数调用将模板参数列表中的 T 设置为 double,将函数参数列表中的 value 设置为 2.15:
show_list0(2.15);
C++提供了一个用省略号表示的元运算符(meta-operator),让您能够声明表示模板参数包的标识符,模板参数包基本上是一个类型列表。同样,它还让您能够声明表示函数参数包的标识符,而函数参数包基本上是一个值列表。其语法如下:
template<typename...Args> // Args is a template parameter pack
void show_list1(Args...args) // args is a function parameter pack
{
...
}
其中,Args 是一个模板参数包,而 args 是一个函数参数包。与其他参数名一样,可将这些参数包的名称指定为任何符合 C++ 标识符规则的名称。Args 和 T 的差别在于,T 与一种类型匹配,而 Args 与任意数量(包括零)的类型匹配。请看下面的函数调用:
show_list1('S', 80, "sweet", 4.5);
在这种情况下,参数包 Args 包含与函数调用中的参数匹配的类型:char、int、const char * 和 double。
下面的代码指出 value 的类型为 T:
void show_list0(T value)
同样,下面的代码指出 args 的类型为 Args:
void show_list1(Args... args) // args is a function parameter pack
更准确地说,这意味着函数参数包 args 包含的值列表与模板参数包 Args 包含的类型列表匹配——无论是类型还是数量。在上面的示例中,args 包含值’S’、80、“sweet” 和 4.5。
这样,可变参数模板 show_list1() 与下面的函数调用都匹配:
show_list1();
show_list1(99);
show_list1(88.5, "cat");
show_list1(2,4,6,8, "who do we", std::string("appreciate));
就最后一个函数调用而言,模板参数包 Args 包含类型 int、int、int、int、const char * 和 std::string,而函数参数包 args 包含值 2、4、6、8、“who do we” 和 std::string(“appreciate”)。
展开参数包
但函数如何访问这些包的内容呢?索引功能在这里不适用,即您不能使用 Args[2] 来访问包中的第三个类型。相反,可将省略号放在函数参数包名的左边,将参数包展开。例如,请看下述有缺陷的代码:
template<typename...Args> // Args is a template parameter pack
void show_list1(Args... args) // args is a function parameter pack
{show_list1(args...); // passes unpacked args to show_list1()
}
这是什么意思呢?为何说它存在缺陷?
假设有如下函数调用:
show_list1(5, 'L', 0.5);
这将把5、‘L’ 和 0.5 封装到 args 中。在该函数内部,下面的调用:
show_list1(5, 'L', 0.5);
将展开成如下所示:
show_list1(5, 'L', 0.5);
也就是说,args 被替换为三个存储在 args 中的值。因此,表示法 args… 展开为一个函数参数列表。不幸的是,该函数调用与原始函数调用相同,因此它将使用相同的参数不断调用自己,导致无限递归(这存在缺陷)。
在可变参数模板函数中使用递归
虽然前面的递归让 show_list1() 成为有用函数的希望破灭,但正确使用递归为访问参数包的内容提供了解决方案。这里的核心理念是,将函数参数包展开,对列表中的第一项进行处理,再将余下的内容传递给递归调用,以此类推,直到列表为空。与常规递归一样,确保递归将终止很重要。这里的技巧是将模板头改为如下所示:
template<typename T, typename... Args>
void show_list3( T value, Args... args)
对于上述定义,show_list3() 的第一个实参决定了 T 和 value 的值,而其他实参决定了 Args 和 args 的值。这让函数能够对 value 进行处理,如显示它。然后,可递归调用 show_list3(),并以 args… 的方式将其他实参传递给它。每次递归调用都将显示一个值,并传递缩短了的列表,直到列表为空为止。下面的程序提供了一种实现,它虽然不完美,但演示了这种技巧。
// variadic1.cpp -- using recursion to unpack a parameter pack
#include<iostream>
#include<string>// definition for 0 parameters -- terminating call
void show_list3() {}// definition for 1 or more parameters
template<typename T, typename... Args>
void show_list3( T value, Args... args) {std::cout << value << ", ";show_list3(args...);
}int main() {int n = 14;double x = 2.71828;std::string mr = "Mr. String objects!";show_list3(n, x);show_list3(x*x, '!', 7, mr);return 0;
}
-
程序说明
请看下面的函数调用:show_list3(x*x, '!', 7, mr);第一个实参导致 T 为 double,value 为 x*x。其他三种类型(char、int 和 std::string)将放入 Args 包中,而其他三个值(‘!’, 7 和 mr)将放入 args 包中。
接下来,函数 show_list3() 使用 cout 显示 value(大约为 7.38905)和字符串“,”。这完成了显示列表中第一项的工作。
接下来是下面的调用:
show_list3(args...);考虑到 args… 的展开作用,这与如下代码等价:
show_list3('!', 7, mr);前面说过,列表将每次减少一项。这次 T 和 value 分别为 char 和 ‘!’,而余下的两种类型和两个值分别被包装到 Args 和 args 中,下次递归调用将处理这些缩小了的包。最后,当 args 为空时,将调用不接受任何参数的 show_list3(),导致处理结束。
上面的程序的输出如下:
14, 2.71828, 7.38905, !, 7, Mr. String objects!, -
改进
可对 show_list3() 做两方面的改进。当前,该函数在列表的每项后面显示一个逗号,但如果能省区最后一项后面的逗号就好了。为此,可添加一个处理一项的模板,并让其行为与通用模板稍有不同:// definition for 1 parameter template<typename T> void show_list3(T value) {std::cout << value << '\n'; }这样,当 args 包缩短到只有一项时,将调用这个版本,而它打印换行符而不是逗号。另外,由于没有递归调用 show_list3(),它也将终止递归。
另一个可改进的地方是,当前的版本按值传递一切。对于这里使用的简单类型来说,这没问题,但对于 cout 可打印的大型类来说,这样做的效率很低。在可变参数模板中,可指定展开模式(pattern)。为此,可将下述代码:
show_list3(Args... args);替换为如下代码:
show_list3(const Args&... args);这将对每个函数参数应用模式 const&。这样,最后分析的参数将不是 std::string mr,而是 const std::string & mr。
下面的程序包含这两项修改。
// variadic2.cpp #include<iostream> #include<string>// definition for 0 parameters; void show_list() {}// definition for 1 parameter template<typename T> void show_list(const T& value) {std::cout << value << '\n'; }// definition for 2 or more parameters template<typename T, typename... Args> void show_list(const T& value, const Args&... args) {std::cout << value << ", ";show_list(args...); }int main() {int n = 14;double x = 2.71828;std::string mr = "Mr. String objects!";show_list(n, x);show_list(x*x, '!', 7, mr);return 0; }该程序的输出如下:
14, 2.71828 7.38905, !, 7, Mr. String objects!
相关文章:
《C++ Primer Plus》第18章:探讨 C++ 新标准(6)
可变参数模板 可变参数模板(variadic template)让您能够创建这样的模板函数和模板类,即可接收可变数量的参数。这里介绍可变参数模板函数。例如,假设要编写一个函数,它可接受任意数量的参数,参数的类型只需…...
.Net Core中使用是SQL Server的邮件发送功能
.Net Core中使用是sqlserver的邮件发送功能准备需求启用SQL Server的电子邮件功能检查和测试在.net Core中调用在sqlsrver的管理中有一个数据库邮件功能,再此可以使用sqlserver来自动发送一些邮件,但是有一些需要插入附件的邮件则需要使用程序代码来解决,下面就是使用C#来调用s…...
Nginx优化服务和防盗链
Nginx优化服务和防盗链一、长连接1、修改主配置文件2、测试3、在主配置文件添加4、验证二、Nginx第三方模块1、开源的echo模块2、查看是否成功3、加echo模块步骤4、网页测试验证三、搭建虚拟主机1、编译安装好nginx后,对主配置文件进行修改2、创建文件3、验证四、防…...
B树与B+树
认识了解MySQL中的B树B树引出什么是B树什么是B树B树的优点B树引出 在MySQL中,如果我们设置了主键, 那么对于该列表中的数据就有了一个索引,插入表中数据的主键值不能重复,而且不能为空. 那当我们插入数据的时候, 它是如何通过索引来判断主键值是否重复的呢? 我们想到它肯定是…...
QEMU网络配置
文章目录1. 前言2. 测试环境3. 配置步骤3.1 host 配置3.1.1 检查 host 对 TUN/TAP 和 网桥的支持情况3.1.2 网桥一端的建立:创建网桥设备,并添加 host 网卡到网桥3.1.3 网桥另一端的建立:TUN/TAP 配置3.2 guest 端的配置4. 参考链接1. 前言 …...
windows安装tomcat
这里写自定义目录标题tomcat官网下载安装包并解压环境变量配置启动tomcat访问http://localhost:8080/修复启动出现乱码问题tomcat官网下载安装包并解压 环境变量配置 系统环境变量新增: 变量名:CATALINA_HOME 变量值:tomcat的安装目录 编辑…...
刷题记录:牛客NC23051华华和月月种树 树链剖分+离线加点
传送门:牛客 题目描述: 华华看书了解到,一起玩养成类的游戏有助于两人培养感情。所以他决定和月月一起种一棵树。因为华华现在也是信息学高手了,所以他们种的树是信息学意义下的。 华华和月月一起维护了一棵动态有根树,每个点有一个权值。刚…...
年薪20W软件测试工程师必备的6大技能(建议收藏)
软件测试 随着软件开发行业的日益发展,岗位需求量和行业薪资都不断增长,想要入行的人也是越来越多,但不知道从哪里下手,今天,就给大家分享一下,软件测试行业都有哪些必会的方法和技术知识点,作…...
【存储】RAID2.0+、多路径技术、磁盘可靠性技术
RAID2.0RAID 2.0技术RAID技术发展RAID 2.0软件逻辑对象RAID 2.0基本原理硬盘域Storage Pool & TierDisk Group(DG)LD(逻辑磁盘)Chunk(CK)Chunk Group(CKG)ExtentGrainVolume &am…...
Vue 2
文章目录1. 简介2. 第一个Vue程序3. 指令3.1 判断循环3.2 操作属性3.3 绑定事件3.4 表单中数据双向绑定3.5 其他内置指令3.6 自定义指令4. 组件4.1 全局注册4.2 局部注册4.3 组件通讯4.4 单文件组件5. 组件插槽5.1 单个插槽5.2 具名插槽5.3 作用域插槽6. 内置组件6.1 component…...
Ubuntu 安装 Docker Engine
【参考】Install Docker Engine on Ubuntu | Docker Documentation: https://docs.docker.com/engine/install/ubuntu/ 【参考】Docker CE 镜像源站-阿里云开发者社区 https://developer.aliyun.com/article/110806 【规范】模仿 Docker 文档,Ubuntu, Docker 首字母…...
SpringBoot入门 - 添加内存数据库H2
上文我们展示了通过学习经典的MVC分包结构展示了一个用户的增删查改项目,但是我们没有接入数据库;本文将在上文的基础上,增加一个H2内存数据库,并且通过Spring 提供的数据访问包JPA进行数据查询。准备知识点在介绍通过Spring JPA接…...
高质量数字化转型创新发展大会暨中国信通院“铸基计划”年度会议成功召开
2023年3月3日,由中国信通院主办的高质量数字化转型创新发展大会暨中国信通院“铸基计划”年度会议在北京成功召开。本次大会深度展示了中国信通院在数字化领域的工作成果,并全面展望了2023年行业的数字化发展趋势。同时,大会发布了中国信通院…...
2023年如何通过软考初级程序员?
初级的考试难度不大,稍微有点编程基础,认真备考应该没什么大问题。 先清楚大纲: 高效备考!理清考点,针对性复习 科目一:综合知识 75道单项选择题,1题1分,时长150分钟;…...
视频自动播放的实现与问题解决
一、前言 页面加载一个视频并且自动播放,这个需求看起来非常简单,实现起来感觉也非常简单;但是,实际做起来还是有几处容易产生问题的地方卡住进度。本文讨论基于Vue3的项目在实现页面加载视频后的自动播放遇到的几个问题。 二、页面实现 页面实现非常简单。在页面上放置一个…...
ThreadLocal 理解及面试
一、ThreadLocal 引用关系 图解关系说明: 每个线程拥有自己的 ThreadLocalMap 属性;ThreadLocalMap 的存储结构为 Entry[] 数组;Entry的Key是ThreadLocal类型且弱引用指向ThreadLocal对象,Value是我们自己定义的泛型值对象&#…...
巾帼绽芬芳 一起向未来(中篇)
编者按:为了隆重纪念纪念“三八”国际妇女节113周年,快来与你全方位、多层次分享交流“三八”国际妇女节的前世今生。分上篇(节日简介、节日发展和节日意义)、中篇(节日活动宗旨和世界各国庆祝方式)和下篇&…...
Qt学习2-Qt Creator新建项目小tips(哔站视频学习记录)
放送两个小tips: 1、MinGW和MSVC的区别 QT学习笔记(二):QT MinGW 和 MSVC 编译方式_Leon_Chan0的博客-CSDN博客 2、如何安装QT对应版本的MSVC (1)问题描述:Qt5.12.8支持MSVC2015和MSVC2017,但是系统安装的是Visual…...
React-高阶组件
认识高级组件 高阶函数的维基百科定义:至少满足以下条件之一 1、接受一个或多个函数作为输入; 2、输出一个函数; JavaScript中比较常见的 filter、map、reduce 都是高阶函数 那么说明是高阶组件呢? 高阶组件的英文是 Higher-Order Components,简称为 HOC;官方的…...
python学习——【第一弹】
前言 Python是一种跨平台的计算机程序设计语言,是ABC语言的替代品,属于面向对象的动态类型语言,最初被设计用于编写自动化脚本,随着版本的不断更新和语言新功能的添加,越来越多被用于独立的、大型项目的开发。 从这篇…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
