[C++11]可变参数模板和参数包展开

可变参数模板

文章目录

    • 可变参数模板
      • 可变参数模板的概念
      • 可变参数模板的定义方式
    • 模板参数包的展开
      • 递归展开参数包
        • sizeof...计算参数包大小
      • 逗号表达式展开参数包
      • enable_if方式展开
      • 折叠表达式展开
    • 总结

可变参数模板的概念

可变参数模板(Variadic templates)C++11新增的最强大的特性之一,它对参数高度泛化,能够让我们创建可以接受可变参数的函数模板和类模板。

  • C++11之前,类模板和函数模板中只能包含固定数量的模板参数,可变模板参数无疑是一个巨大的改进,但由于可变参数模板比较抽象,因此使用起来需要一定的技巧。
  • 在C++11之前其实也有可变参数的概念,比如printf函数就能够接收任意多个参数,但这是函数参数的可变参数,并不是模板的可变参数。

可变参数模板的定义方式

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

上面的我们把带…的模板参数称为模板参数包(template parameter pack)

上面这个函数模板的参数 args 前面有省略号,我们称之为模板参数包(template parameter pack)的可变模版参数,它里面包含了0到N个模版参数,而我们是无法直接获取 args 中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数。

模板参数包的展开

递归展开参数包

递归展开的方式如下:

  • 函数模板除去参数包可变模板参数外至少要有一个模板参数,用于从参数包中拿出一个参数
  • 剩余参数包递归调用
  • 每次剥离一个参数,直至参数包空
template <class T, class... Args>
void foo(T first, Args... args)
{cout << first << " ";foo(args...);
}
//int main()
//foo("good", 2, "hello", 4, 110);

我们写出如上一个带可变模板参数的函数foo,我们尝试在main函数中调用发现无法调用

有递归自然有出口,当函数包空时,仍会递归一个空包作为参数,而我们没有空参数函数,所以我们还要再增加一个空参数的函数来进行特化

如下:

void foo()
{cout << endl;
}template <class T, class... Args>
void foo(T first, Args... args)
{cout << first << " ";foo(args...);
}int main()
{foo(1, 2, 3, 4);foo("good", 2, "hello", 4, 110);return 0;
}
//输出
//1 2 3 4 
//good 2 hello 4 110

当然我们可以规定递归出口为其他数量的参数,如:

void foo(int a)
{cout << endl;
}template <class T, class... Args>
void foo(T first, Args... args)
{cout << first << " ";foo(args...);
}int main()
{foo(1, 2, 3, 4);foo("good", 2, "hello", 4, 110);return 0;
}
//输出
//1 2 3 
//good 2 hello 4

当然,当我们定义递归出口为一个参数的函数时,我们调用foo必须传入不少于一个参数

sizeof…计算参数包大小

我们其实是可以计算参数包的大小的,如sizeof…(args)

void foo()
{cout << endl;
}
template <class T, class... Args>
void foo(T first, Args... args)
{cout << first << " " << sizeof...(args) << endl;foo(args...);
}int main()
{foo(1, 2, 3, 4);return 0;
}
//输出
//1 3
//2 2
//3 1
//4 0

那么我们是否可以通过对参数包大小的判断来结束函数递归,从而省去无参数或者少参数函数作为递归出口呢?

template <class T, class... Args>
void foo(T first, Args... args)
{cout << first << " " << sizeof...(args) << endl;if (!sizeof...(args))return;foo(args...);
}

我们发现直接报错了,也就是说这种方式不可行

  • 我们在学习函数模板时知道,函数模板并不能直接调用,函数模板需要在编译时根据传入的实参类型进行推演,生成对应的函数,这个生成的函数才能够被调用。
  • 而这个推演过程是在编译时进行的,我们函数体中递归调用函数,也就是说推演会不断继续下去,仍会进行参数包为空的函数推演,此时就会报错了,因为我们的函数至少要有一个参数,而又没有重载的空参数函数了
  • 这里的if判断是在代码编译结束后,运行代码时才会所走的逻辑,也就是运行时逻辑,而函数模板的推演是一个编译时逻辑。

逗号表达式展开参数包

逗号表达式展开包其实是利用了C++11新特性,列表初始化。

我们列表初始化的原理就是先用列表构建initializer_list,再用initializer_list去构建我们的容器

如果我们把参数包放入初始化列表中会怎样呢?

template <class... Args>
void foo(Args... args)
{initializer_list<int> a{args...};for (auto x : a)cout << x << " ";
}
//输出
//1 2 3 4 

我们发现参数包放入初始化列表中,由于初始化列表从左往右执行,参数包中的参数会被逐个取出,此时由于没有递归展开,所以我们不需要再额外定义空参数的重载函数,传入参数也没有数目限制。

利用初始化列表和逗号表达式结合,我们可以如下展开参数包:

template <class... Args>
void foo(Args... args)
{(void)initializer_list<int>{(cout << args << " ", 0)...};
}int main()
{foo(1, 2, 3, 4, "GenshinImpact", 3.14);return 0;
}
//输出
//1 2 3 4 GenshinImpact 3.14

我们发现很顺利的输出了,甚至不受类型限制

其实剖析一下发现逗号表达式是一种很犯规的写法,我们逗号表达式的返回值是最右边的表达式,也就是0,所以最终用来初始化列表的元素是0,但是由于列表初始化要从左向右执行,所以我们的参数包会被展开,假如参数包有N个参数,我们展开N次,但是此次返回值都是0,所以得到了N个0的列表,而参数包内的内容都被输出了。

enable_if方式展开

enable_if是C++11新引入的一个结构体,定义如下:

  // Primary template./// Define a member typedef @c type only if a boolean constant is true.template<bool, typename _Tp = void>struct enable_if{ };// Partial specialization for true.template<typename _Tp>struct enable_if<true, _Tp>{ typedef _Tp type; };

我们可以看出下面是上面的一个偏特化。当我们传入第一个参数为true时会用第二个模板来实例化,将_Tp typedef为type,而第一个模板什么也没做。

故而enable_if常用于需要根据不同的类型的条件实例化不同模板的情形。也就是说,在不同条件下选用不同类型,其广泛的应用在 C++ 的模板元编程(meta programming)之中,利用的就是SFINAE原则,英文全称为Substitution failure is not an error,意思就是匹配失败不是错误,假如有一个特化会导致编译时错误,只要还有别的选择,那么就无视这个特化错误而去选择另外的实现。

因而我们可以借此来解决我们递归展开函数包递归出口函数和参数限制的问题。

具体流程就是:

  • 利用参数包构建tuple(元组)
  • 以下标访问元组元素,同时利用下标是否等于元组元素个数作为条件重载两个函数
  • 当下标小于value,那么对对应下标元素操作
  • 当下标等于value,则进入对应函数体

代码如下:

template <size_t k = 0, class tup>
typename enable_if<k == std::tuple_size<tup>::value>::type _foo(const tup &t)
{cout << endl;
}
template <size_t k = 0, class tup>typename enable_if < k<std::tuple_size<tup>::value>::type _foo(const tup &t)
{cout << get<k>(t) << " ";_foo<k + 1, tup>(t);
}template <class... Args>
void foo(Args... args)
{_foo<0>(make_tuple(args...));
}int main()
{foo(2023, "GenshinImpact", "hello", 2024);return 0;
}

优雅,实在是太优雅了。

折叠表达式展开

前面几种都是C++11的内容,而我们的折叠表达式(Fold Expressions)则是我们C++17的新语法特性,使用折叠表达式可以简化对C++11中引入的参数包的处理,可以在某些情况下避免使用递归,更加方便的展开参数。

如下示例:

template <class... Args>
void foo(Args... args)
{(cout << ... << args) << endl;
}int main()
{foo(2023, "GenShinImpact", "hello");return 0;
}

简洁了不少,但是如何格式化呢?需要增加格式化辅助函数。

template <class T>
string format(const T &t)
{stringstream ss;ss << " " << t << " ";return ss.str();
}template <class... Args>
void foo(Args... args)
{(cout << ... << format(args)) << endl;
}int main()
{foo(2023, "GenShinImpact", "hello");return 0;
}
//输出
// 2023  GenShinImpact  hello 

也可以直接利用逗号表达式进行简化

template <class... Args>
void foo(Args... args)
{(cout << ... << (cout << args, " ")) << endl;
}int main()
{foo(2023, "GenShinImpact", "hello");return 0;
}
//输出
//2023 GenShinImpact hello 

括号里逗号表达式的返回值是" “,当输出完从参数包里拆出的args,返回” "给左边的输出流输出

总结

可变参数模板参数高度泛化,提高了编程的泛用性。

而为了实现可变参数模板我们引入了参数包,于是需要对参数包进行展开,我们可以:

  1. 通过递归每次拆出一个参数,展开参数包
  2. 利用初始化列表展开参数包
  3. 通过enable_if和元组结合展开参数包
  4. C++17直接引入折叠表达式,便于展开参数包

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

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

相关文章

leetcode——设计循环队列

设计循环队列 这个题目在这里小编只分享一个解题思路&#xff0c;因为还有一个思路小编还在尝试&#xff0c;一直过不了&#xff0c;还在这里不断尝试&#xff0c;等我试出来的时候我在分享给大家&#xff0c;首先我们在这里给出的是数组的形式&#xff0c;后面在分享单链表的思…

开发盲盒商城的意义

开发盲盒商城的意义在于为电商行业带来新的增长机会&#xff0c;满足消费者对购物方式趣味性的需求&#xff0c;同时提升用户的参与度&#xff0c;为商家带来更多销售机会和增强影响力的机遇。 盲盒商城系统通过独特的盲盒玩法&#xff0c;为用户带来了全新的趣味购物体验&…

ProTable高级表格获取表单数据

隐藏高级表格中的收起按钮 手动控制高级表格中的搜索按钮 获取高级表格中的表单数据 Forminstance 引入 然后在代码中定义 const refForm useRef(); 使用 refForm.current.getFileDsValue();

单体进化微服务:拆分、注册、调用、网关、过滤、治理、分布式事务

这里写目录标题 基本介绍生产-消费-网关父依赖生产者服务消费者服务网关服务common服务 感想 基本介绍 Spring Cloud 是一个用于构建分布式系统和微服务架构的开发工具包。它提供了一系列的功能和组件&#xff0c;用于解决微服务架构中的常见问题&#xff0c;如服务注册与发现…

Git精讲

Git基本操作 创建Git本地仓库 git initgit clone 配置Git git config [--global] user.name "Your Name" git config [--global] user.email "emailexample.com"–global是一个可选项。如果使用了该选项&#xff0c;表示这台机器上所有的Git仓库都会使…

算法设计与分析算法实现——动态规划最大子段

输入:整数序列a1,a2,…,an 输出:序列的一个子段,其和Σak最大 注意:当所有整数都为负数时,定义最大子段和为0 使用动态规划,输入数组是a[n]; 状态转移方程dp[i]=max(dp[i-1]+a[i],a[i])——这个状态方程可以发现,使得满足“连续”这一要求的重点在于每个dp[i]都包含了当…

快时尚品牌Halara登上TikTok美国小店榜Top 5,运动健身风靡TikTok

TikTok Shop美国电商数据周榜&#xff08;11/06-12&#xff09;已出&#xff0c;具体信息如下&#xff1a; 上周总GMV达到5850万美元&#xff0c;日均出单840万美元&#xff1b;单日出单最高达2110万美元&#xff0c;是当前美国单日最高销售额&#xff1b; 截至11月12日&…

SpringBoot :ch01 项目结构

前言 在本系列博客中&#xff0c;我们将深入探讨 Spring Boot 项目结构的各个方面&#xff0c;并探讨如何利用这些结构来构建健壮、可扩展的应用程序。通过深入了解 Spring Boot 项目结构&#xff0c;我们可以更好地利用 Spring Boot 的优势&#xff0c;提高开发效率&#xff…

泛型编程:进阶的正确打开方式

泛型编程进阶建议书籍 《C新经典模板与泛型编程》 - 微信阅读 《C20高级编程》 - 微信阅读 《深入实践C模板编程》- 微信阅读 《C Template中文版》- 微信阅读

多目标应用:基于非支配排序的蜣螂优化算法NSDBO求解微电网多目标优化调度(MATLAB)

一、微网系统运行优化模型 微电网优化模型介绍&#xff1a; 微电网多目标优化调度模型简介_IT猿手的博客-CSDN博客 二、基于非支配排序的蜣螂优化算法NSDBO 基于非支配排序的蜣螂优化算法NSDBO简介&#xff1a; https://blog.csdn.net/weixin46204734/article/details/128…

云服务器,你了解多少?揭秘其神秘面纱

在这个信息爆炸的时代&#xff0c;我们的生活已经离不开互联网。云服务器&#xff0c;顾名思义&#xff0c;就是将服务器放在云端&#xff0c;让用户可以通过互联网随时随地访问。那么&#xff0c;云服务器有哪些特点呢?让我们一起来揭开它的神秘面纱。 首先&#xff0c;我们…

【ARM AMBA AXI 入门 13 -- AXI 协议中 RRESP 信号详细介绍】

请阅读【ARM AMBA AXI 总线 文章专栏导读】 文章目录 AXI 协议中 RRESP 信号RRESP 使用举例RRESP 3bit 使用AXI 协议中 RRESP 信号 在 AXI (Advanced eXtensible Interface) 协议中,RRESP 信号用来表示读取事务的响应状态,它由从设备(Slave)发往主设备(Master)来通知读…

C#源代码生成器深入讲解二

在阅读本文前需掌握源代码生成器相关知识C#源代码生成器深入讲解一 C#源代码生成器深入讲解二—增量生成器 源代码生成器有个非常大的弊病&#xff0c;每次都会遍历所有的语法树来分析,这样就有个问题&#xff0c;每次可能只修改了很少一部分或者只有很少一部分的代码需要分析…

维基百科文章爬虫和聚类【二】:KMeans

维基百科是丰富的信息和知识来源。它可以方便地构建为带有类别和其他文章链接的文章&#xff0c;还形成了相关文档的网络。我的 NLP 项目下载、处理和应用维基百科文章上的机器学习算法。 一、说明 在我的上一篇文章中&#xff0c;展示了该项目的轮廓&#xff0c;并奠定了其基础…

C#中的is和as的使用和区别

目录 概述一、is操作符1. is操作符的语法2. is操作符的用途3. is操作符的使用示例4. is操作符与typeof操作符的区别 二、as操作符1. as操作符的语法2. as操作符的用途3. as操作符的使用示例4. as操作符与is操作符的区别和联系5. as操作符与is操作符的区别总结 概述 在C#编程语…

深度学习卷积神经网络垃圾分类系统 - 深度学习 神经网络 图像识别 垃圾分类 算法 小程序 计算机竞赛

文章目录 0 简介1 背景意义2 数据集3 数据探索4 数据增广(数据集补充)5 垃圾图像分类5.1 迁移学习5.1.1 什么是迁移学习&#xff1f;5.1.2 为什么要迁移学习&#xff1f; 5.2 模型选择5.3 训练环境5.3.1 硬件配置5.3.2 软件配置 5.4 训练过程5.5 模型分类效果(PC端) 6 构建垃圾…

c++语言核心及进阶

核心编程 内存分区模型 根据c执行将内存划分为5个区域&#xff1a; 代码区&#xff0c;存放函数体的二进制&#xff0c;即CPU执行的机器指令&#xff0c;并且是只读的&#xff1b;常量区&#xff0c;存放常量&#xff0c;即程序运行期间不能被改变的量。全局区&#xff08;静…

巧用 tabindex 属性解决 iframe 环境下鼠标点击元素之外的监听事件

场景如下&#xff1a;用 js 模拟了一个右键菜单&#xff0c;在鼠标点击到右键菜单之外的区域时&#xff0c;关闭菜单。 之前的处理方案是&#xff0c;打开右键菜单时在 body 上注册一个监听事件&#xff0c;addEventListener(click, closeMenu) 。但是这种方式在遇到 iframe 时…

练习八-利用有限状态机进行时序逻辑的设计

利用有限状态机进行时序逻辑的设计 1&#xff0c;任务目的&#xff1a;2&#xff0c;RTL代码&#xff0c;及原理框图3&#xff0c;测试代码&#xff0c;输出波形 1&#xff0c;任务目的&#xff1a; &#xff08;1&#xff09;掌握利用有限状态机实现一般时序逻辑分析的方法&am…

淘宝商品详情接口,商品属性接口,商品信息查询,商品详细信息接口,h5详情,淘宝APP详情

淘宝商品详情API接口可以使用淘宝开放平台提供的SDK或API来获取。这些接口可以用于获取商品的详细信息&#xff0c;如标题、价格、描述、图片等。 以下是使用淘宝开放平台API获取商品详情的步骤&#xff1a; 注册淘宝开放平台账号&#xff0c;并创建应用&#xff0c;获取应用…