[C++11]可变参数模板

导览:

  1. 本章将从可变参数模板的概念开始讲起,到其究竟是如何做到实例化的
  2. 再从实例出发,探究该如何编写可变参数模板
  3. 最后涉及可变参数模板的运用
    在这里插入图片描述

什么是可变参数模板


让我们先见一下可变参数模板

template<typename ...Args>
void test(Args... args)
{//...
}

概念:

一个可变参数模板(variadic template)就是一个接受可变数目参数的模板函数或模板类。可变数目的参数称为参数包(parameter packet)。存在两种参数包:模板参数包(template parameter packet),表示零个或多个模板参数;函数参数包(function parameter packet),表示零个或多个函数参数。

几个问题:

让我们带着以下几个问题去学习可变参数模板

  1. 可变参数模板如何实例化
  2. 如何书写可变参数模板
  3. 可变参数模板的运用—emplace系列
  4. 可变参数模板的运用—包扩展
  5. 可变参数模板的运用—转发参数包

可变参数模板如何实例化


上代码:

template<typename T, typename ...Args>
void func(const T& t)
{cout << t << endl << endl;
}
template<typename T, typename ...Args>
void func(const T& t, const Args&... args)
{cout << t << endl;func(args...);
}
int main()
{func(1,2,'c',4);func(1,2,'c');func(1,2);func(1);return 0;
}

代码思路:

这里书写了一个名为func()的函数,其形参中包含了一个函数参数包args。在main函数中,分别以不同的参数数目调用了func(),然后打印结果如下

1
2
c
41
2
c1
21

解释:

这里拿func(1,2,'c',4)来说明。

先明确一点,这些实例化需要在编译阶段进行,到了运行阶段就挨个去调用参数符合的函数就行了。

可以这样理解:

当我func传参时,我的接收方是func(const T& t, const Args&... args),我传递的是一个参数包,但其实这个包就是一连串的参数(args… -> {int,int,char,int}),接收时将我传递包中的第一个元素给给t,后面的元素又形成了一个新的参数包,然后一直递归去调用实例化,最后调用到只剩一个参数时就会去调用我们写的func(),不再实例化生成新的func()(我们特意增加了单参数的func(),此时编译器就不会自己实例化生成新的,而这个func()里面不会再递归实例化任何别的版本)。

func这个函数调用总共实例化生成了4个不同版本的函数,他们分别是:

void func(const int&,const int&,const char&,const int&);
void func(const int&,const char&,const int&);
void func(const char&,const int&);
void func(const int&);

如下是运行中的调用逻辑:

调用targs…包中参数个数向下传参
func(1,2,‘c’,4)12,‘c’,43个参数的包2,‘c’,4
func(2,‘c’,4)2‘c’,42个参数的包‘c’,4
func(‘c’,4)‘c’41个参数的包4
func(4)4null空包无传参

解决几个小问题:

先引入一个运算符:sizeof...

这个运算符可以计算包中含有多少个元素,其返回值是一个常量表达式。具体代码如下:sizeof…(args)

  1. 为什么实例化递归时不能用ifsizeof...判断参数包中的元素是否为空,进而跳出循环。

    答:因为if是运行时判断,而实例化是在编译阶段进行的,所以不能用if判断

  2. 既然args…是一个参数包,那么我能不能用args[i]的方式打印这个包中的某个参数

    答:不能。同样,[]是运行时解析,而编译完后args…就不再是一个包,而是一堆参数了,自然也就不可以下标访问

如何书写可变参数模板


书写模板参数一直都是一件很苦恼的事 ,因为这个...总是不知道放在何处

建议在写的时候多试试几种方式,哪种不报错就用哪种。以下给出几点建议,以供参考:

  1. ...总是跟在参数名或类型名后面
  2. 如果想要声明模板参数包,则跟在类型名后面
  3. 如果想要展开函数参数包,则跟在参数名后面
  4. 还有某些特殊情况,多换换位置也就过了

例子:

template<typename T, typename... Args>  //声明模板参数包
void fooHelper(T t, Args... args) {  //声明模板参数包// 处理t  fooHelper(args...); // 递归调用,展开剩余参数  
}  
template<typename T>  
void fooHelper(T t) {  // 终止递归的情况  
}  
template<typename... Args>  
void foo(Args... args) {  fooHelper(args...); // 初始调用,展开所有参数  
}

可变参数模板的运用


emplace系列

这是C++11后STL库中新增的函数

template <class... Args>void emplace_back (Args&&... args);
template <class... Args>
iterator emplace (const_iterator position, Args&&... args);template <class... Args>void emplace_back (Args&&... args);
template <class... Args>iterator emplace (const_iterator position, Args&&... args);
//...

STL库中对此的解释是:Construct and insert element,构造同时插入元素

这里涉及了右值引用(但不是重点),如果想了解右值引用可以去看我的另一篇文章[C++11]右值引用

我们给出一个最简单的例子来说明:

class Date
{
public:Date(int a = 1, int b = 1, int c = 1):_a(a), _b(b), _c(c){cout << "Date()" << endl;}Date(const Date& d){cout << "const Date&" << endl;}Date(Date&& d){cout << "const Date&&" << endl;}
private:int _a;int _b;int _c;
};
int main()
{list<Date> lt1;list< pair<Date, Date>> lt2;Date d1(10,10,10);lt1.push_back(d1);lt1.push_back(move(d1));cout << "==================================" << endl;lt1.emplace_back(d1);lt1.emplace_back(move(d1));cout << "==================================" << endl;lt1.push_back({10,10,10}); cout << endl;lt1.emplace_back(10,10,10);cout << "==================================" << endl;pair<Date, Date> pr({1,1,1},{1,1,1});lt2.push_back(pr);cout << endl;lt2.push_back(move(pr));cout << "==================================" << endl;lt2.emplace_back(pr);cout << endl;lt2.emplace_back(move(pr));cout << "==================================" << endl;lt2.push_back({ (1, 1, 1), (1, 1, 1) });cout << endl;lt2.emplace_back((1, 1, 1), (1, 1, 1));return 0;
}

命令行打印结果:

Date() --构造
const Date&  --拷贝构造
const Date&& --移动构造
==================================
const Date&  --拷贝构造
const Date&& --移动构造
==================================
Date() --构造
const Date&& --移动构造Date() --构造
==================================
Date() --构造
Date() --构造
const Date&  --拷贝构造
const Date&  --拷贝构造
const Date&  --拷贝构造
const Date&  --拷贝构造const Date&& --移动构造
const Date&& --移动构造
==================================
const Date&  --拷贝构造
const Date&  --拷贝构造const Date&& --移动构造
const Date&& --移动构造
==================================
Date() --构造
Date() --构造
const Date&& --移动构造
const Date&& --移动构造Date() --构造
Date() --构造
插入方式vector lt1vector<pair<Date,Date>> lt1
push_back(d1),push左值const Date& --拷贝构造const Date& --拷贝构造
const Date& --拷贝构造
emplace_back(d1),emplace左值const Date& --拷贝构造const Date& --拷贝构造
const Date& --拷贝构造
push_back(move(d1)),push右值const Date&& --移动构造const Date&& --移动构造
const Date&& --移动构造
emplace_back(move(d1)),emplace右值const Date&& --移动构造const Date&& --移动构造
const Date&& --移动构造
push_back({10,10,10}),push用initial_listDate() --构造
const Date&& --移动构造
null
emplace_back(10,10,10),emplace用可变模板参数Date() --构造null
push_back({(10,10,10),(10,10,10)}),push用initial_listnullDate() --构造
Date() --构造
const Date&& --移动构造
const Date&& --移动构造
emplace_back((1, 1, 1), (1, 1, 1)),emplace用可变模板参数nullDate() --构造
Date() --构造

如果有朋友下定决心也尝试一下,结果可能会发现,哎?…怎么我的push和emplace一点规律都没有???甚至会发生错误!

这很有可能是你用的不是list,而是vector之类的顺序容器,这就不得不谈到vector的扩容问题了,对!这都是由于扩容搞的鬼,扩容导致了资源的重新分配。请记住:因为扩容问题导致vector和list的push、emplace的底层实现是很不同的。

是不是看起来很复杂,其实一点也不简单!但是我们只需要记住如下几点,就能明白emplace是用来干嘛的了

  1. 记住!emplace的作用就是利用可变模板参数优化效率

  2. 对于类创建的对象,无论是用push还是用emplace他们的结果都一样(例如上表前4个)。这是由于push里面也实现了右值引用版本(很显然,他们的效率只和左值、右值引用相关),并且用对象初始化与今天所讲的参数包没有任何关系。

  3. 最后4个,两两一组相比较,很明显能发现push和emplace版本相差甚大,这就是我们的可变参数模板优化的成果!其原理也很简单:

    emplace中用接受的参数包直接去构造Date

    而push版本中则需要在进push前先构造Date,再将参数传入

    但其实也还好,毕竟push都实现了右值引用版本,传入参数的时候会调用移动构造,开销也不会太大。

包扩展

  • 包扩展(Pack Expansion)是C++编程中的一个概念,它主要应用在模板元编程中,特别是可变参数模板函数中。包扩展允许程序员将参数包(parameter pack)展开为一系列独立的参数,以便在函数模板或类模板中使用。
  • 对于一个参数包,除了获取其大小外,我们能对他做的唯一的事情就是扩展(expand)它。当扩展一个包时,我们还要提供用于每个扩展元素的模式

大家回顾一下前面的内容,其实我们一开始实例化打印的例子,就是一种包扩展

举一个比实例化打印更复杂的例子:

//如果你需要对某些信息进行处理(如错误信息),我们可以实现出将这个包中的每一个参数都当作实参传进一个函数
template <typename... Args>
ostream& errorMsg(ostream &os,const Args&...rest)
{return print(os,debug(rest)...);//debug是一个函数
}
//上述print就好像我们写如下代码
print(os,debug(x1),debug(x2),debug(x2)...);//这里的省略号就是省略号

转发参数包

这里就简要说明一下

转发参数包(Forwarding Parameter Pack)是C++模板编程中的一个高级特性,它允许你将参数包原封不动地传递给其他函数或模板,同时保持参数的原始类型和值类别(左值或右值)。这在编写通用包装器(wrappers)或代理(delegates)时特别有用,因为你可以确保参数在传递过程中不会被不必要地拷贝或移动。像前面的emplace就是使用该技术。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/785910.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SpringCloud】一文详谈Nacos

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》《项目实战》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 …

Linux之用户账号、用户组和与账号有关的系统文件

目录 一、基本介绍 1.用户和用户组 2.UID和GID 二、 账户管理 1.查看用户的UID和GID 2.添加账户 3.删除账号 4.修改账号 5.账户口令 三、分组管理 1.新增用户组 2.删除用户组 3.修改用户组 4.用户组切换 四、与账号有关的系统文件 1./etc/passwd 2./etc/shado…

李宏毅深度强化学习导论——当奖励是稀疏的

引言 这是李宏毅强化学习的笔记&#xff0c;主要介绍如何处理稀疏奖励问题。 稀疏奖励 当我们拿Actor和环境互动后可以得到很多奖励&#xff0c;整理之后可以得到分数 A A A&#xff0c;然后可以训练Actor。 但RL中有时会出现多数情况下奖励为零&#xff0c;此时我们不知道动…

行存储与列存储:大数据存储方案的选择与优缺点分析

随着大数据时代的来临&#xff0c;数据的规模和复杂性呈指数级增长&#xff0c;传统的关系数据库已经不再适应这一巨大的存储量和计算要求。在大数据存储领域&#xff0c;行存储和列存储成为两种备受关注的存储方案。本文将探讨行存储和列存储的定义、优缺点&#xff0c;并结合…

第十四届省赛大学B组(C/C++)岛屿个数

目录 题目链接&#xff1a;岛屿个数 解题思路&#xff1a; AC代码&#xff08;BFSDFS&#xff09;&#xff1a; 题目链接&#xff1a;岛屿个数 小蓝得到了一副大小为 MN 的格子地图&#xff0c;可以将其视作一个只包含字符 0&#xff08;代表海水&#xff09;和 1&#xff0…

LeetCode-331. 验证二叉树的前序序列化【栈 树 字符串 二叉树】

LeetCode-331. 验证二叉树的前序序列化【栈 树 字符串 二叉树】 题目描述&#xff1a;解题思路一&#xff1a;看提示主要是栈和树。这题其实不是二叉树的遍历题&#xff0c;而是检验二叉树基础知识的题&#xff0c;也许有些难想。第一种解法是&#xff1a;把有效的叶子节点使用…

【DETR系列目标检测算法代码精讲】01 DETR算法03 Dataloader代码精讲

与一般的Dataloader的区别在于我们对图像进行了随机裁剪&#xff0c;需要进行额外的操作才能将其打包到dataloader里面 这一段的代码如下&#xff1a; if args.distributed:sampler_train DistributedSampler(dataset_train)sampler_val DistributedSampler(dataset_val, shu…

Python 自学(九) 之异常处理,文件及目录操作

目录 1. try ... except ... else ... finally 排列 P231 2. write, read, seek, readline, readlines 基本文件操作 P245 3. os模块 基本目录操作 P249 4. os.path 模块 复杂目录操作 P250 5. os 模块 高…

Spring之循环依赖

什么是循环依赖? 依赖的相互引用,如下列的这种形式 Component public class A {Autowiredprivate B b;}Component public class B {Autowiredprivate A a; } Spring是如何解决循环依赖的 Spring是通过三级缓存来解决循环依赖 singletonObjects : 单例bean,已经实例化,完成…

牛客2024年愚人节比赛(A-K)

比赛链接 毕竟是娱乐场&#xff0c;放平心态打吧。。。 只有A一个考了数学期望&#xff0c;其他的基本都是acmer特有的脑筋急转弯&#xff0c;看个乐呵即可。 A 我是欧皇&#xff0c;赚到盆满钵满&#xff01; 思路&#xff1a; 我们有 p 1 p_1 p1​ 的概率直接拿到一件实…

Redis改造原始代码

基础篇Redis 5.2.2.改造原始代码 代码说明: 1.在我们完成了使用工厂设计模式来完成代码的编写之后&#xff0c;我们在获得连接时&#xff0c;就可以通过工厂来获得。 &#xff0c;而不用直接去new对象&#xff0c;降低耦合&#xff0c;并且使用的还是连接池对象。 2.当我们…

FreeROST作业day2

1.总结串口的发送和接收功能使用到的函数 串口发送数据函数&#xff1a; HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout); UART_Handle…

【LeetCode】热题100:排序链表

题目&#xff1a; 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4] 示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,3,4,5] …

linux进程fork函数的讲解。

通过指令,查看接口的详细信息 man forkOn success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately. 这里的返回值的意…

FPGA设计_加法器

文章目录 前言补充&#xff1a;各种门电路符号一、半加器二、全加器三、串行进位加法器3.1、verilog代码设计 四、超前进位加法器4.1、verilog代码设计 五、进位链CARRY4 前言 在之前一篇介绍7系列FPGA底层资源的时候&#xff0c;我们提到过每一个slice当中有一个CARRY4&#…

玫瑰图和雷达图(自备)

目录 玫瑰图 数据格式 绘图基础 绘图升级&#xff08;文本调整&#xff09; 玫瑰图 下载数据data/2020/2020-11-24 mirrors_rfordatascience/tidytuesday - 码云 - 开源中国 (gitee.com) R语言绘图—南丁格尔玫瑰图 - 知乎 (zhihu.com) 数据格式 rm(list ls()) libr…

2024年新算法-冠豪猪优化算法(CPO),CPO-RF-Adaboost,CPO优化随机森林RF-Adaboost回归预测-附代码

冠豪猪优化算法&#xff08;CPO&#xff09;是一种基于自然界中猪群觅食行为启发的优化算法。该算法模拟了猪群在寻找食物时的集群行为&#xff0c;通过一系列的迭代过程来优化目标函数&#xff0c;以寻找最优解。在这个算法中&#xff0c;猪被分为几个群体&#xff0c;每个群体…

CA根证书——https安全保障的基石

HTTPS通信中&#xff0c;服务器端使用数字证书来证明自己的身份。客户端需要验证服务器发送的证书的真实性。这就需要一个可信的第三方机构&#xff0c;即CA&#xff0c;来颁发和管理证书。CA根证书是证书颁发机构层次结构的顶级证书&#xff0c;客户端信任的所有证书都可以追溯…

python实现泊松回归

1 什么是基于计数的数据&#xff1f; 基于计数的数据包含以特定速率发生的事件。发生率可能会随着时间的推移或从一次观察到下一次观察而发生变化。以下是基于计数的数据的一些示例&#xff1a; 每小时穿过十字路口的车辆数量每月去看医生的人数每月发现的类地行星数量 计数数…

行车记录打不开?别慌,数据恢复有高招!

行车记录打不开&#xff0c;这恐怕是许多车主都曾经遭遇过的烦恼。在驾驶途中&#xff0c;行车记录仪本应是记录美好瞬间、保障行车安全的重要工具&#xff0c;但一旦它出现打不开的情况&#xff0c;所有的期待与信赖便瞬间化为乌有。面对这种情况&#xff0c;我们该如何应对&a…