【C++】C++11可变参数模板(函数模板、类模板)

2023-05-16

在C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板。可变参数模板的加入使得C++11的功能变得更加强大,而由此也带来了许多神奇的用法。


可变参数模板

可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typenameclass后面带上省略号...

template<typename... Types>

其中,...可接纳的模板参数个数是0个及以上的任意数量,需要注意包括0个

若不希望产生模板参数个数为0的变长参数模板,则可以采用以下的定义:

template<typename Head, typename... Tail>

本质上,...可接纳的模板参数个数仍然是0个及以上的任意数量,但由于多了一个Head类型,由此该模板可以接纳1个及其以上的模板参数


函数模板的使用

在函数模板中,可变参数模板最常见的使用场景是以递归的方法取出可用参数

void print() {}

template<typename T, typename... Types>
void print(const T& firstArg, const Types&... args) {
	std::cout << firstArg << " " << sizeof...(args) << std::endl;
	print(args...);
}

通过设置...,可以向print函数传递任意个数的参数,并且各个参数的类型也是任意。也就是说,可以允许模板参数接受任意多个不同类型的不同参数。这就是不定参数的模板,格外需要关注的是,...三次出现的位置

如果如下调用print函数:

print(2, "hello", 1);

如此调用会递归将3个参数全部打印。细心的话会发现定义了一个空的print函数,这是因为当使用可变参数的模板,需要定义一个处理最后情况的函数,如果不写,会编译错误。这种递归的方式,是不是觉得很惊艳!

在不定参数的模板函数中,还可以通过如下方式获得args的参数个数:

sizeof...(args)

假设,在上面代码的基础上再加上一个模板函数如下,那么运行的结果是什么呢?

#include <iostream>

void print() {}

template<typename T, typename... Types>
void print(const T& firstArg, const Types&... args) {
	std::cout << firstArg << " " << sizeof...(args) << std::endl;
	print(args...);
}

template <typename... Types>
void print(const Types&... args) {
  std::cout << "print(...)" << std::endl;
}

int main(int argc, char *argv[]) {
	print(2, "hello", 1);

	return 0;
}

现在有一个模板函数接纳一个参数加上可变参数,还有一个模板函数直接接纳可变参数,如果调用print(2, “hello”, 1),会发现这两个模板函数的参数格式都符合。是否会出现冲突、不冲突的话会执行哪一个呢?

运行代码后的结果为:

yngzmiao@yngzmiao-virtual-machine:~/test/$ ./main 
2 2
hello 1
1 0

从结果上可以看出,程序最终选择了一个参数加上不定参数的模板函数。也就是说,当较泛化和较特化的模板函数同时存在的时候,最终程序会执行较特化的那一个

再比如一个例子,std::max函数只可以返回两个数的较大者,如果多个数,就可以通过不定参数的模板来实现:

#include <iostream>

template <typename T>
T my_max(T value) {
  return value;
}

template <typename T, typename... Types>
T my_max(T value, Types... args) {
  return std::max(value, my_max(args...));
}

int main(int argc, char *argv[]) {
  std::cout << my_max(1, 5, 8, 4, 6) << std::endl;

	return 0;
}

类模板的使用

除了函数模板的使用外,类模板也可以使用不定参数的模板参数,最典型的就是tuple类了。其大致代码如下:

#include <iostream>

template<typename... Values> class tuple;
template<> class tuple<> {};

template<typename Head, typename... Tail>
class tuple<Head, Tail...>
  : private tuple<Tail...>
{
  typedef tuple<Tail...> inherited;
  public:
    tuple() {}
    tuple(Head v, Tail... vtail) : m_head(v), inherited(vtail...) {}
    Head& head() {return m_head;}
    inherited& tail() {return *this;}
  protected:
    Head m_head;
};

int main(int argc, char *argv[]) {
	tuple<int, float, std::string> t(1, 2.3, "hello");
	std::cout << t.head() << " " << t.tail().head() << " " << t.tail().tail().head() << std::endl;

	return 0;
}

根据代码可以知道,tuple类继承除首之外的其他参数的子tuple类,以此类推,最终继承空参数的tuple类。继承关系可以表述为:

tuple<>
      ↑
tuple<std::string>
  string "hello"
      ↑
tuple<float, std::string>
  float 2.3
      ↑
tuple<int, float, std::string>
  int 1

接下来考虑在内存中的分布,内存中先存储父类的变量成员,再保存子类的变量成员,也就是说,对象t按照内存分布来说;

┌─────────┐<---- 对象指针
|  hello  |
|─────────|
|  2.3    |
|─────────|
|  1      |
└─────────┘

这时候就可以知道下一句代码的含义了:

inherited& tail() {return *this;}

tail()函数返回的是父类对象,父类对象和子类对象的内存起始地址其实是一样的,因此返回*this,再强行转化为inherited类型。

当然,上面采用的是递归继承的方式,除此之外,还可以采用递归复合的方式:

template<typename... Values> class tup;
template<> class tup<> {};

template<typename Head, typename... Tail>
class tup<Head, Tail...>
{
  typedef tup<Tail...> composited;
  public:
    tup() {}
    tup(Head v, Tail... vtail) : m_head(v), m_tail(vtail...) {}
    Head& head() {return m_head;}
    composited& tail() {return m_tail;}
  protected:
    Head m_head;
    composited m_tail;
};

两种方式在使用的过程中没有什么区别,但C++11中采用的是第一种方式(递归继承)。

在上面的例子中,取出tuple中的元素是一个比较复杂的操作,需要不断地取tail,最终取head才能获得。标准库的std::tuple,对此进行简化,还提供了一些其他的函数来进行对tuple的访问。例如:

#include <iostream>
#include <tuple>

int main(int argc, char *argv[]) {
  std::tuple<int, float, std::string> t2(1, 2.3, "hello");
  std::get<0>(t2) = 4;                      // 修改tuple内的元素
  std::cout << std::get<0>(t2) << " " << std::get<1>(t2) << " " << std::get<2>(t2) << std::endl;    // 获取tuple内的元素

  auto t3 = std::make_tuple(2, 3.4, "World");         // make方法生成tuple对象
  
  std::cout << std::tuple_size<decltype(t3)>::value << std::endl;    // 获取tuple对象元素的个数
  std::tuple_element<1, decltype(t3)>::type f = 1.2;          // 获取tuple对象某元素的类型

	return 0;
}

相关阅读

  • C++标准库元组(tuple)源码浅析
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++】C++11可变参数模板(函数模板、类模板) 的相关文章

  • C/C++ 数学库文件 (math.h)

    目录 1 三角函数 Trigonometric functions 1 1 cos 函数 1 2 sin 正弦函数 1 3 tan 正切函数 1 4 acos 反余弦函数 1 5 asin 反正弦函数 1 6 atan 反正切函数 1 7
  • C语言进阶 ~ 内存四区(栈、堆、全局、代码区)

    特别声明 xff1a 该部分是根据B站大佬 什么都想干好的视频学习而来 目录 1 1 数据类型本质分析 1 1 1 数据类型概念 1 1 2 数据类型的本质 1 1 3 数据类型的别名 1 1 4 数据类型之 void 1 2 变量的本质分
  • C语言进阶 ~ 一级指针与字符串

    目录 2 1 指针强化 2 2 一级指针 char 易错地方 2 2 1 对空字符串和非法字符串的判断 2 2 2 越界 2 2 3 指针的叠加会不断改变指针的方向 2 2 4 局部变量不要外传 2 2 5 函数内使用辅助变量的重要性 2
  • STC51从入门到精通(汇编)~~~ 第八讲:串行通信技术

    目录 8 1 80C51单片机串行通信技术的特点 8 2 串行通信基本知识 8 2 1 数据通信 8 2 2 串行通信的传输方式 8 2 3 异步通信和同步通信 8 3 串行接口的组成和特性 8 3 1 串行口的结构 8 3 2 串行口控制
  • 基于arduino的循迹小车(含有PID算法)

    循迹小车一般分为两方面 xff1a 一方面是简单的闭环赛道只有直道和弯道 xff0c 另一方面是毕设类型的包括一些元素 xff1a 90度弯道 十字道路 S形弯道等 1 CSDN下载 xff1a 含有PID xff1a https down
  • 基于手机蓝牙的arduino遥控小车

    遥控小车是每个人童年的最爱 xff0c 不仅好奇它的奇妙 xff0c 更是喜欢它带来的刺激 小编为大家带来几篇博客 xff0c 来给大家讲讲制作遥控小车的程序 看大标题可知我们一共有五个方法去制作一款带有遥控功能的小车 xff0c 小编分开
  • 课程设计题四:LED彩灯控制器设计

    要求 xff1a 1 至少10个发光管4种花样自动变换 xff0c 循环往复 2 彩灯花样变换的快慢节拍可以手动和自动方式控制 xff0c 手动控制按钮按一次转换一次 xff1b 自动控制方式每15秒变换一次 xff0c 1分钟循环一遍 3
  • PyCharm 调试Debug入门

    为了摈弃print的暴力调试法 xff0c 在mentor的耳濡目染下 xff0c 我开始了PyCharm的Debug 例程 xff1a def helloworld print 39 hello world 39 a 61 1 b 61
  • 树莓派自带SSH server,但默认关闭,需手动开启ssh

    命令行输入sudo raspi config 选择Interfacing Options 选择SSH项 enable
  • Ubuntu 18.04 ——— VINS-Fusion运行与EVO的评测与使用

    Ubuntu 18 04 VINS Fusion运行与EVO的评测与使用 一 运行环境搭建1 VINS Fusion安装1 创建ros工作空间2 编译VINS Fusion 2 EVO安装3 数据集 二 VINS Fusion运行1 单相机
  • Deep Learning 最优化方法之Momentum(动量)

    本文是Deep Learning 之 最优化方法系列文章的Momentum xff08 动量 xff09 方法 主要参考Deep Learning 一书 整个优化系列文章列表 xff1a Deep Learning 之 最优化方法 Deep
  • VINS-Mono代码详解 ——— (0)原理框图 + ROS 基础知识 + 代码目录图

    VINS Mono代码详解 xff08 0 xff09 原理框图与代码流程图 一 VINS原理图1 前端 xff08 数据预处理 xff09 2 后端 xff08 滑窗优化 xff09 3 初始化4 闭环 二 ROS 基础知识1 运行VIN
  • C++Primer第五版 ——— (ch2)课后习题参考答案

    C 43 43 Primer第五版 xff08 ch2 xff09 课后习题参考答案 练习 2 1练习 2 2练习 2 3练习2 5练习2 6练习2 72 8 练习练习 2 9练习 2 10练习 2 11练习 2 12练习 2 13练习 2
  • Ubuntu 18.04 ———(Intel RealSense D435i)标定后结果用于VINS-Fusion

    Ubuntu 18 04 xff08 Intel RealSense D435i xff09 标定后结果用于VINS Fusion 一 相机内外参1 什么是相机内外参 xff1f 2 如何获得相机标定前的内参 xff1f 3 如何把标定参数
  • C语言——全局变量在多个.c文件中共用

    全局变量可以定义在一个 c文件中 xff0c 变量存储在静态存储区 xff0c 变量可以被其他文件中的函数使用 xff0c 变量的作用范围是整个程序 xff0c 全局变量可以再被使用过的地方改变数值 当定义了全局变量的时候 xff0c 在其
  • JavaScript中的防抖和节流

    防抖 debounce 触发高频事件后 n秒内 函数只执行一次 如果n秒内高频事件再次触发 xff0c 则重新计算事件 防抖场景 xff1a 1 登录 发短信等按钮避免用户点击太快 xff0c 以至于发送多次请求 xff0c 需要防抖 2
  • 推荐系统之ROC和AUC详解

    前言 这个绝对是重量级内容了 xff0c 也是很基础的东西 对于模型来讲 xff0c 不同的阈值会有不同的泛化能力 xff0c 这时候 xff0c 如果想比较两个模型的泛化能力 xff0c 这个阈值的高低也会受到影响 xff0c 所以就需要
  • teleport助手下载启动了但是一直显示未检测到

    teleport助手下载启动了 xff0c 但是一直显示未检测到teleport助手 刷新浏览器 xff0c 这边一直显示未能检测到 点开助手设置发现 xff0c 端口号为50020 xff0c 无法显示 http 127 0 0 1 50
  • TongWeb7本地部署(Windows)

    问 xff1a 上来就先问 xff0c 什么是TongWeb xff08 东方通 xff09 答 xff1a 简单一句国产化容器 xff0c 类似Weblogic xff0c Tomcat 我想大家不会平白无故的了解TongWeb xff0
  • 本地TongWeb7部署web(SpringCloud)项目(Windows)

    本地TongWeb7部署web的前提是 xff0c 本地能启动TongWeb7 xff08 Windows xff09 xff0c 具体的教程下面给出教程 TongWeb7本地部署 xff08 Windows xff09 you来有去的博客

随机推荐