[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.rhkb.cn/news/294041.html

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

相关文章

iOS开发进阶(十一):ViewController 控制器详解

文章目录 一、前言二、UIViewController三、UINavigationController四、UITabBarController五、UIPageViewController六、拓展阅读 一、前言 iOS 界面开发最重要的首属ViewController和View&#xff0c;ViewController是View的控制器&#xff0c;也就是一般的页面&#xff0c;…

《Invariant Feature Learning for Generalized Long-Tailed Classification》阅读笔记

论文标题 《Invariant Feature Learning for Generalized Long-Tailed Classification》 广义长尾分类的不变特征学习 作者 Kaihua Tang、Mingyuan Tao、Jiaxin Qi、Zhenguang Liu 和 Hanwang Zhang 来自南洋理工大学、阿里达摩院和浙江大学 初读 摘要 属性不平衡&#…

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

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

Oracle Solaris 11.3开工失败问题处理记录

1、故障现像 起初是我这有套RAC有点问题&#xff0c;我想重启1个节点&#xff0c;结果发现重启后该节点的IP能PING通&#xff0c;但SSH连不上去&#xff0c;对应的RAC服务也没有自动启动。 操作系统是solaris 11.3。由于该IP对应的主机是LDOM&#xff0c;于是我去主域上telnet…

【linux】基础IO(一)

文件只有站在系统层面才能彻底理解 简单回顾一下文件&#xff1a; 首先我们要明确一点&#xff0c;我们说的打开文件不是写下fopen就打开文件&#xff0c;而是当我们的进程运行起来&#xff0c;进程打开的文件。 我们在C语言一般都会使用过如下的代码进行向文件中写入 但是除…

LLM应用:Prompt flow vs LangChain

背景 Prompt flow和LangChain都是LLM时代&#xff0c;为高效地构建LLM应用而生。 Prompt flow是Microsoft开源的&#xff0c;其诞生时&#xff0c;LangChain已经很有名气了。 所以作为后生的Prompt flow会为我们带来哪些新的东西呢&#xff1f; ​​​​​​​ Prompt flo…

互联网、因特网、万维网的区别

互联网 internet&#xff1a;凡是能彼此通信的设备组成的网络就叫互联网&#xff0c;即使只有两台计算机&#xff0c;无论以何种技术使其彼此通信&#xff0c;都叫互联网。所以&#xff0c;根据互联网的覆盖规模可以分为&#xff1a; 局域网&#xff08;Local Area Network&am…

如何使用potplayer在公网环境访问内网群晖NAS中储存在webdav中的影视资源

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-D7WJh3JaNVrLcj2b {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

黑马鸿蒙笔记 3

目录 11.ArkUI组件-Column和Row 12.ArkUI组件-循环控制 13.ArkUI组件-List 14.ArkUI组件-自定义组件 15.ArkUI组件-状态管理State装饰器 16.ArkUI组件-状态管理-任务统计案例 17.ArkUI组件-状态管理-PropLinkProvideConsume 11.ArkUI组件-Column和Row Colum和Row的交叉…

第三天开始写了

现在的情况 写俩个接口信息 1. 一个修改 2. 一个 删除 发现了一个问题 只有这些参数无法完成修改的 因为这些关联到一个商品表和一个用户表&#xff0c;我们应该查询他们id信息&#xff0c;修改其中的内容&#xff0c;单独根据字符串查看效果可能不好 这里我们提交应该是用…

2024年抖音小店的保证金是多少?真的可以做0元保证金的店铺吗?

大家好&#xff0c;我是电商糖果 2024年想要入驻抖音小店的商家依旧很多&#xff0c;关于小店的保证金问题也有不少人前来咨询。 大家问的最多的是可以开通0元保证金的店铺吗&#xff1f;以及2024年抖音小店保证金是多少&#xff1f; 这里糖果给大家一个个解答。 可以开通0…

基于YOLOv8的绝缘子检测系统

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文摘要&#xff1a;基于YOLOv8的绝缘子小目标检测&#xff0c;阐述了整个数据制作和训练可视化过程 1.YOLOv8介绍 Ultralytics YOLOv8是Ultralytics公司开发的YOLO目标检测和图像分割模型的最新版本。YOLOv8是一种尖端的、最先进的&a…

k8s入门到实战(七)—— 回顾:使用yaml文件配置pv、pvc、configmap部署mysql服务

实战&#xff1a;部署 mysql 服务 回顾加深 pv、pvc、configmap 删除所有 deployment、pv、pvc、configmap、StorageClass创建一个 nsf 挂载目录给 mysql mkdir -p /nfs/data/mysql创建 yaml 文件mysql-server.yaml # 创建pv apiVersion: v1 kind: PersistentVolume metadat…

针对 qt的sqlite加密数据库sqlitecipher插件QtCipherSqlitePlugin

&#x1f482; 个人主页:pp不会算法^ v ^ &#x1f91f; 版权: 本文由【pp不会算法v】原创、在CSDN首发、需要转载请联系博主 &#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 文章目录 简介编译安装使用可视化工具查看完结 简介 在客户端存储…

字符指针、字符串、字符数组、字符串数组等

参考&#xff1a;https://xiefor100.blog.csdn.net/article/details/52667734 #include <stdio.h> #include <stdlib.h> #include <string.h> int main() {char s1[] "12345"; // "12345"在栈区&#xff0c;可以指针偏移读取和修改c…

stable diffusion如何下载预处理器?

如何下载预处理器&#xff1f; 具体位置:SD文件>extensions>sd-webui-controlnet>annotator” 把整个文件夹复制到SD的文件夹里面 里面有一个“downloads”文件夹 把这些模型复制到“downloads”文件夹里

【MATLAB第103期】#源码分享 | 基于MATLAB的LIME可解释性线性分类预测模型,2020b以上版本

【MATLAB第103期】#源码分享 | 基于MATLAB的LIME可解释性线性分类预测模型&#xff0c;2020b以上版本 一、模型介绍 LIME&#xff08;Local Interpretable Model-agnostic Explanations&#xff09;是一种用于解释复杂机器学习模型预测结果的算法。它由Marco Ribeiro、Sameer…

并发-开启新线程

目录 实现多线程的官方正确方法&#xff1a;2种 实现Runnable接口方式的实现原理 两种方法的对比 匿名内部类实现线程的两种方式 思考&#xff1a;同时用两种方法会怎么样 总结&#xff1a;最精准的描述 实现多线程的官方正确方法&#xff1a;2种 方法一&#xff1a;实现…

Git、TortoiseGit、SVN、TortoiseSVN 的关系和区别

Git、TortoiseGit、SVN、TortoiseSVN 的关系和区别 &#xff08;二&#xff09;Git&#xff08;分布式版本控制系统&#xff09;:&#xff08;二&#xff09;SVN&#xff08;集中式版本控制系统&#xff09;&#xff08;三&#xff09;TortoiseGit一、下载安装 git二、安装过程…

[Java基础揉碎]接口

目录 为什么有接口 基本介绍 接口的应用场景 注意事项和细节 接口和继承类的比较 总结 >接口和继承解决的问题不同 >接口比继承更加灵活 >接口在一定程度上实现代码解耦 接口的多态特性 多态参数 ​编辑 多态数组 多态传递 ​编辑 为什么有接口 usb插槽就是…