可变参数模板 - c++11

news2024/12/24 13:28:40

文章目录:

  • 可变参数模板的认识
  • 参数包的展开
    • 递归函数方式展开参数包
    • 逗号表达式展开参数包
  • STL容器中的empalce相关接口函数

可变参数模板的认识

c++11 引入了可变参数模板(variadic templates)的特性,使得编写支持任意数量参数的模板变得更加简单灵活。

c++11 的可变参数模板能够让你创建可以接受可变参数的函数模板和类模板,相比 c++98/03 ,类模板和函数模板中只能包含固定数量的模板参数,可变模板参数无疑是一个巨大的改进。然而由于可变参数模板比较抽象,使用起来需要一定的技巧,所以使用的不是很多。

下面就是一个基本可变参数的函数模板:

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args... args,这个参数包可以包含0到任意个模板参数
template<class ...Args>
void ShowList(Args... args)
{}

说明:

  • 模板参数 Args 前面有省略号,表示它是一个可变模板参数,我们把带省略号的参数称为 “参数包”,它里面包含了 0 ~ N (N>=0) 个模板参数。args 则是一个函数形参参数包。
  • 我们无法直接获取参数包 args 中的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变参数模板参数的一个主要特点,也是最大的难点,即如何展开可变模板参数。
  • 语法不支持使用 args[i] 这样的方式来获取可变参数。
  • 模板参数包 Args 和函数形参参数包 args 的名字也是可以指定的,并不是固定的。

现在,可通过 ShowList 函数,传递任意数量和类型的参数,如下:

int main()
{
	ShowList();
	ShowList(5);
	ShowList(3, 3.14, 'C', "template");
	return 0;
}

我们可以通过 sizeof... 来获取函数模板参数包的个数:

template<class ...Args>
int getArgsCount(Args... args)
{
	return sizeof...(args);
}

int main()
{
	cout << getArgsCount(3, 3.14, 'C', "template") << endl;  // 运行测试结果为:4
	return 0;
}

参数包的展开

递归函数方式展开参数包

方式一:

递归展开参数包的方式如下:

  • 给函数模板增加一个模板参数,这样就可以从参数包中分离出一个参数出来。
  • 在函数模板中递归调用该函数模板,调用时传入剩余的参数包。
  • 一直递归,每次取出参数包中的一个参数,直到参数中的所有参数被取出。

取出参数包中的每个参数并打印,函数模板的写法如下:

template<class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " "; // 打印取出的参数
	ShowList(args...);    // 递归调用,将参数包传递下去
}

使用次函数的时候出现一个问题,即如何终止函数的递归调用。

无参的递归终止函数

// 无参递归终止函数
template<class T>
void ShowList() { cout << endl; }

template<class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " "; // 打印取出的参数
	ShowList(args...);    // 递归调用,将参数包传递下去
}

当递归调用 ShowList 函数模板时,若传入的参数包中的参数个数为 0 时,就会匹配到这个无参的递归终止函数,这样就解决了终止递归的问题。

完整代码如下:

//递归终止函数
void ShowList()
{
	cout << endl;
}

template<class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " "; //打印传入的若干参数中的第一个参数
	ShowList(args...); //将剩下参数继续向下传
}

int main()
{
	ShowList();
	ShowList(5);
	ShowList(3, 3.14, 'C', "template");
	return 0;
}

带参的递归终止函数

// 匹配无参的调用
template<class ...Args>
void ShowList(Args... args) {}

// 带参递归终止函数
template<class T>
void ShowList(const T& t)
{
	cout << t << endl;
}

template<class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " "; // 打印取出的参数
	ShowList(args...);    // 递归调用,将参数包传递下去
}

在调用 ShowList 函数时,若传入的参数包中的参数个数为 1 时,则匹配到该递归终止函数,也就解决递归终止的问题了。

方式二:

递归展开参数包的方式说明:

  • 定义一个函数模板,它接收一个参数包 Args 和一个整数 index 作为模板参数。
  • 在函数模板中,使用 if constexpr 语句对 index 是否为 0 进行判断。若 index 不为 0,则将递归调用该函数模板,将 index 减 1,并将参数包中剩余的参数传递给下一次调用。

示例:

void ShowList() {}

template<class T,class... Args>
void ShowList(T value, Args ...args)
{
	if constexpr (sizeof...(args) == 0)
		cout << value << endl;
	else {
		// 递归调用ShowList函数,将参数包中的剩余参数传递给下一次调用
		cout << value << " ";
		ShowList(args...);
	}
}

int main()
{
	ShowList();
	ShowList(5);
	ShowList(3, 3.14, 'C', "template");
	return 0;
}

if constexpr 是一种 c++17 语言扩展。

逗号表达式展开参数包

逗号表达式展开参数包是指在函数调用时,使用逗号表达式将多个参数打包成一个参数包,并将该参数包展开为多个独立的此参数传递给函数。

在 c++ 中,逗号表达式可以用于展开参数包。逗号表达式的结果是其最后一个表达式的值。

示例:

// 逗号表达式展开参数包
void ShowList(){}

template<class T>
void PrintArg(T t)
{
	cout << t << " ";
}

template<class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args),0)... };
	cout << endl;
}

int main()
{
	ShowList();
	ShowList(5);
	ShowList(3, 3.14, 'C', "template");
	return 0;
}

这段代码演示了 c++ 中的逗号表达式展开参数包的功能。这段代码中,定义了一个可变参数模板函数 ShowList 和一个辅助函数 PrintArg 。ShowList 函数可以接收任意数量和类型的参数,并将它们展开成一个逗号分隔的列表,最终输出到控制台上面。

  • 这种展开参数包的方式,不需要通过递归终止函数,是直接在 expand 函数体中展开的,PrintArg 不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。逗号表达式会按顺序执行逗号前面的表达式。
  • expand 函数中的逗号表达式:(PrintArg(args), 0),也是按照这个执行顺序,先执 PrintArg(args) ,再得到逗号表达式的结果 0。
  • 同时还用到了 C++11 的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…} 将会展开成 ((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为 0 的数组 int arr[sizeof…(Args)]。
  • 由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分 PrintArg(args) 打印出参数,也就是说在构造 in t数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

STL容器中的empalce相关接口函数

c++11 标准对 STL 中的容器增加了 emplace 版本的插入接口,这些接口允许我们在容器中就地构造对象,而不需要手动创建对象并传递给插入函数。

list 容器中的 push_front、push_back、和 insert 函数,都增加了对应的 emplace 版本,如下:

在这里插入图片描述

这些 emplace 版本的插入接口支持模板的可变参数,list 容器的声明如下:

在这里插入图片描述

因为 emplace 版本接口的可变参数模板参数的类型是万能引用,因此既可以接收左值对象,也可以接收右值对象,还可以接收参数包。

  • 若调用 emplace 版本接口时传入的是左值对象,那么首先调用构造函数实例化出一个左值对象,然后使用定位 new 表达式调用构造函数对空间进行初始化,将匹配到拷贝构造函数。
  • 若调用 emplace 版本接口时传入的是右值对象,那么首先调用构造函数实例化出一个右值对象,然后使用定位 new 表达式调用构造函数对空间进行初始化,将匹配到移动构造函数。
  • 如果调用 emplace 版本接口传入的是参数包,那么直接调用函数进行插入,并使用定位 new 表达式调用构造函数对空间进行初始化,将匹配到构造函数。

emplace 版本插入函数的使用示例:

int main()
{
	std::list< std::pair<int, char> > mylist;
	// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
	// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
	mylist.emplace_back(10, 'a');
	mylist.emplace_back(20, 'b');
	mylist.emplace_back(make_pair(30, 'c'));
	mylist.push_back(make_pair(40, 'd'));
	mylist.push_back({ 50, 'e' });
	for (auto e : mylist)
		cout << e.first << ":" << e.second << endl;
	return 0;
}

emplace 系列接口的意义在于提高代码的效率和可读性,尤其是在需要频繁插入大量元素的情况下,使用它们可以提高代码的性能。

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

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

相关文章

交易想简化分析并少失误,波浪原则anzo capital认为必不可少

要想在交易中简化分析并少失误&#xff0c;不管是交易新手还是交易高手&#xff0c;anzo capital认为其实很容易&#xff0c;只要了解艾略特波浪原则。 艾略特波浪原则&#xff0c;每一个趋势都由特定的基本元素(波浪)组成&#xff0c;这些元素具有重复的趋势。这些波浪可以根…

企业或人力资源公司可利用直播将职位以视频直播的方式展现

抖音直播招聘报白是一种通过直播方式展示职位信息并与求职者互动的招聘方式。抖音的短视频流量能够让岗位信息覆盖更广泛的人群&#xff0c;增加招聘信息的曝光度。通过抖音的短视频流量红利和精准推送&#xff0c;能够提高岗位信息的曝光度和求职者的留存率。如果你想做招聘报…

Windows系统安装node-red

Quick Start 1. Install Node.js 第一步下载node.js,超链接在后面 Download the latest LTS version of Node.js from the official Node.js home page. It will offer you the best version for your system. Run the downloaded MSI file. Installing Node.js requires l…

TCP的三次握手、四次挥手!就像打电话一样简单!

目录 学前必会 三次握手详解 和打电话一样 为什么必须要三次&#xff1f; 四次挥手详解 和挂电话一样 为什么要四次挥手&#xff1f; 第四次为何要等待 2*MSL&#xff1f; 相关面试题&#xff1a; 说一下三次握手、四次挥手的过程三次握手四次挥手的目的是什么&#x…

明基护眼台灯怎么样?明基、书客、欧普护眼台灯对比测评

不得不说&#xff0c;如今我国儿童青少年总体近视率非常高&#xff0c;甚至超过了50%&#xff01;其中6岁儿童为14.3%&#xff0c;小学生为35.6%&#xff0c;初中生为71.1%&#xff0c;高中生为80.5%&#xff0c;而造成如此高的近视率的原因主要是不良的学习、生活用眼习惯&…

VMware虚拟机安装Ubuntu22.04教程(2023最新最详细)

目录 简介 1 VMware虚拟机下载与安装 2 Ubuntu操作系统安装与配置 2.1 Ubuntu虚拟机配置 2.2 Ubuntu操作系统安装 简介 Linux是一种自由和开放源代码的操作系统内核&#xff0c;被广泛应用于各种计算机系统中。它以稳定性、安全性和灵活性而闻名&#xff0c;并成为服务器…

科学计算语言Julia编程初步

文章目录 安装基本类型和计算函数初步条件和判断循环向量计算 Julia号称有着比肩C的速度&#xff0c;同时又像Python一样便捷的编程语言&#xff0c;非常适合科研狗使用。之前写了很多博客介绍Julia在数值分析中的应用&#xff0c;这次写一个适合初学者学习的Julia教程系列。 …

客户转化率太低?CRM客户管理系统来帮您

客户是否准确真实、销售跟进策略是否有效、销售跟进流程是否及时等&#xff0c;这些都是影响客户转化的因素。为了提高客户转化率&#xff0c;不少企业开始使用CRM销售管理系统。下面说说销售如何通过CRM系统提高客户转化率&#xff1f; 1、CRM能够识别不同渠道线索质量 CRM系…

开源CasaOS云软件发现关键漏洞

近日&#xff0c;开源 CasaOS 个人云软件中发现的两个严重的安全漏洞。该漏洞一旦被攻击者成功利用&#xff0c;就可实现任意代码执行并接管易受攻击的系统。 这两个漏洞被追踪为CVE-2023-37265和CVE-2023-37266&#xff0c;CVSS评分均为9.8分。 发现这些漏洞的Sonar安全研究…

【数据结构】线性表(十)队列:循环队列及其基本操作(初始化、判空、判满、入队、出队、存取队首元素)

文章目录 队列1. 定义2. 基本操作 顺序队列循环队列1. 头文件和常量2. 队列结构体3. 队列的初始化4. 判断队列是否为空5. 判断队列是否已满6. 入队7. 出队8. 存取队首元素9. 获取队列中元素个数10. 打印队列中的元素9. 主函数10. 代码整合 堆栈Stack 和 队列Queue是两种非常重要…

首次扭亏为盈后,货拉拉还想靠造车更上一层楼?

前阵子&#xff0c;一句【货拉拉“拉不拉”拉布拉多&#xff0c;取决于货拉拉“拉”拉布拉多时拉布拉多“拉”得多不多】的趣梗在网上掀起了一波热潮。而热梗背后的主角货拉拉&#xff0c;近期又透露出了谋求港股上市的消息&#xff0c;进而在市场上又掀起了一波热潮。 招股书…

Java拦截器(Interceptor)和过滤器(Filter)实例详解

一、Java过滤器和拦截器 1.1、过滤器(Filter) Filter过滤器&#xff0c;是Servlet&#xff08;Server Applet&#xff09;技术中的技术&#xff0c;开发人员可以通过Filter技术&#xff0c;管理web资源&#xff0c;可以对指定的一些行为进行拦截&#xff0c;例如URL级别的权限…

程序员的金饭碗在哪里?这几个网站建议收藏!帮助你一步登天

俗话说的好&#xff0c;一个趁手的工具抵过诸葛亮。尤其是在程序员这个领域&#xff0c;不仅是一个非常和科技挂钩的领域&#xff0c;而且更新速度非常的迅速。 连java python都在更新&#xff0c;手头上写码的工具却还是老三样怎可行&#xff1f;这就需要我们跟上时代的脚步&…

全局下载报错怎么办

举个例子&#xff0c;当你要全局下载create-react-app&#xff0c;报如下图所示的错 这个时候&#xff0c;关闭掉git base,再以管理员身份运行 你再次下载&#xff0c;ok了

【网络】对于我前面UDP博客的补充

UDP 前言正式开始UDP报文UDP报文如何将UDP报文和报头进行分离和封装UDP如何将有效载荷交付给上层如何提取出完整报文报头是啥报头中的检验和 UDP的特点IO接口乱序问题UDP是全双工的注意事项基于UDP的应用层协议 再次谈论端口五元组端口号范围划分netstatxargs 前言 本篇比较偏…

C#调用C++ 的DLL传送和接收中文字符串

1 c#向c传送中文字符串 设置&#xff1a;将 字符集 改为 使用多字节字符集 cpp代码&#xff1a; extern "C"_declspec(dllexport) int input_chn_str(char in_str[]) {cout<<in_str<<endl;return 0; }c#代码&#xff1a; [DllImport("Demo.dll…

uni-app:引用文件的方法

绝对定位 ①import common from "/utils/common.js" ②import common from "utils/common.js" <template><view></view> </template> <script>import common from "/utils/common.js"export default {data() {ret…

分享一下抽奖活动小程序怎么做

在当今数字化时代&#xff0c;抽奖活动小程序已成为一种高效、创新的营销方式。它不仅能够吸引用户的注意力&#xff0c;提高品牌知名度&#xff0c;还能促进用户参与度&#xff0c;增强用户对品牌的忠诚度。本文将详细介绍如何制作一个成功的抽奖活动小程序&#xff0c;以及它…

[yolo系列:如何固定随机种子(以yolov7为例)]

文章目录 概要随机种子&#xff08;Random Seed&#xff09;第一步第二步第三步第四步 概要 在计算机科学和深度学习领域&#xff0c;随机性是一个常见而重要的概念。在一些情况下&#xff0c;我们需要确保代码每次运行时都能得到相同的随机结果&#xff0c;以便进行模型的可重…

SAR ADC:10 bit 串口控制的模数转换器MS1549,可替TLV1549

MS1549 是一个 10 位开关电容器、逐次逼近型的模数转换 器。此芯片有 2 个数字输入端、 1 个三态输出口&#xff08;包括片选端 口 ( CS ) 、 1 个 I/O CLOCK 端口和 1 个数字输出端 (DATA OUT) &#xff09;&#xff0c; 可以实现三总线接口到总控制器的串行口的数据传输…