掌握C++模板的艺术:类型参数、默认值和自动推导
掌握C++模板的艺术:类型参数、默认值和自动推导
模板参数
类型模板参数
在 Grid
示例中,Grid
模板有一个模板参数:存储在网格中的类型。编写类模板时,您需要在尖括号内指定参数列表,例如:
template <typename T>
这个参数列表类似于函数或方法中的参数列表。与函数和方法一样,你可以编写具有任意多个模板参数的类。此外,这些参数不必是类型,它们可以有默认值。
非类型模板参数
非类型参数是普通参数,如整数和指针——这类参数你可能已经在函数和方法中很熟悉了。然而,非类型模板参数只能是整型(char
、int
、long
等)、枚举类型、指针、引用、std::nullptr_t
、auto
、auto&
和 auto*
。C++20 还允许浮点类型和类类型的非类型模板参数。后者有很多限制,在本文中不再详细讨论。
在 Grid
类模板中,你可以使用非类型模板参数来指定网格的高度和宽度,而不是在构造函数中指定。在模板列表中指定非类型参数而不是在构造函数中指定的主要优点是这些值在代码编译之前就已知。回想一下,编译器通过在编译之前替换模板参数来生成模板实例的代码。因此,你可以在实现中使用普通的二维数组,而不是动态调整大小的向量数组。以下是带有更改的新类定义:
export template <typename T, size_t WIDTH, size_t HEIGHT>
class Grid {
public:Grid() = default;virtual ~Grid() = default;// 明确默认复制构造函数和赋值运算符。Grid(const Grid& src) = default;Grid& operator=(const Grid& rhs) = default;std::optional<T>& at(size_t x, size_t y);const std::optional<T>& at(size_t x, size_t y) const;size_t getHeight() const { return HEIGHT; }size_t getWidth() const { return WIDTH; }private:void verifyCoordinate(size_t x, size_t y) const;std::optional<T> m_cells[WIDTH][HEIGHT];
};
注意,模板参数列表需要三个参数:存储在网格中的对象类型,以及网格的宽度和高度。宽度和高度用于创建存储对象的二维数组。下面是类方法的定义:
// 类方法定义
template <typename T, size_t WIDTH, size_t HEIGHT>
void Grid<T, WIDTH, HEIGHT>::verifyCoordinate(size_t x, size_t y) const {if (x >= WIDTH) {throw std::out_of_range { std::format("{} must be less than {}.", x, WIDTH) };}if (y >= HEIGHT) {throw std::out_of_range { std::format("{} must be less than {}.", y, HEIGHT) };}
}template <typename T, size_t WIDTH, size_t HEIGHT>
const std::optional<T>& Grid<T, WIDTH, HEIGHT>::at(size_t x, size_t y) const {verifyCoordinate(x, y);return m_cells[x][y];
}template <typename T, size_t WIDTH, size_t HEIGHT>
std::optional<T>& Grid<T, WIDTH, HEIGHT>::at(size_t x, size_t y) {return const_cast<std::optional<T>&>(std::as_const(*this).at(x, y));
}
注意,之前你在哪里指定了 Grid<T>
,现在你必须指定 Grid<T, WIDTH, HEIGHT>
来指定三个模板参数。你可以这样实例化并使用这个模板:
Grid<int,10, 10> myGrid;
Grid<int, 10, 10> anotherGrid;
myGrid.at(2, 3) = 42;
anotherGrid = myGrid;
cout << anotherGrid.at(2, 3).value_or(0);
这段代码看起来很棒,但不幸的是,存在比你最初预期的更多限制。首先,你不能使用非常量整数来指定高度或宽度。以下代码无法编译:
size_t height { 10 };
Grid<int, 10, height> testGrid; // 无法编译
然而,如果你将高度定义为常量,则可以编译:
const size_t height { 10 };
Grid<int, 10, height> testGrid; // 可编译并工作
具有正确返回类型的 constexpr
函数也可以工作。例如,如果你有一个返回 size_t
的 constexpr
函数,你可以用它来初始化高度模板参数:
constexpr size_t getHeight() { return 10; }
...
Grid<double, 2, getHeight()> myDoubleGrid;
第二个限制可能更重要。现在宽度和高度是模板参数,它们是每个网格类型的一部分。这意味着 Grid<int,10,10>
和 Grid<int,10,11>
是两种不同的类型。你不能将一种类型的对象赋值给另一种类型的对象,也不能将一种类型的变量传递给期望另一种类型变量的函数或方法。
注意:非类型模板参数成为实例化对象类型规范的一部分。
类模板参数的默认值
设置高度和宽度的默认值
如果您继续使用高度和宽度作为模板参数的方法,您可能想为 Grid<T>
类构造函数中之前的高度和宽度非类型模板参数提供默认值。C++ 允许您使用类似的语法为模板参数提供默认值。同时,您也可以为 T
类型参数提供默认值。下面是类定义:
export template <typename T = int, size_t WIDTH = 10, size_t HEIGHT = 10>
class Grid {// 其余部分与之前版本相同
};
在方法定义的模板规范中,您不需要为 T
、WIDTH
和 HEIGHT
指定默认值。例如,这是 at()
方法的实现:
template <typename T, size_t WIDTH, size_t HEIGHT>
const std::optional<T>& Grid<T, WIDTH, HEIGHT>::at(size_t x, size_t y) const {verifyCoordinate(x, y);return m_cells[x][y];
}
现在,您可以在没有任何模板参数的情况下实例化 Grid
,只需指定元素类型,元素类型和宽度,或元素类型、宽度和高度:
Grid<> myIntGrid;
Grid<int> myGrid;
Grid<int, 5> anotherGrid;
Grid<int, 5, 5> aFourthGrid;
请注意,如果您不指定任何类模板参数,您仍然需要指定一组空的尖括号。例如,以下代码无法编译!
Grid myIntGrid;
类模板参数列表中默认参数的规则与函数或方法相同;也就是说,您可以从右边开始为参数提供默认值。
类模板参数推导(CTAD)
自动推导模板参数
类模板参数推导允许编译器自动从传递给类模板构造函数的参数推导出模板参数。例如,标准库中有一个名为 std::pair
的类模板,在 <utility>
中定义,并在第1章中介绍。pair
存储两个可能不同类型的值,通常需要指定为模板参数。例如:
pair<int, double> pair1 { 1, 2.3 };
为了避免编写模板参数,可以使用一个名为 std::make_pair()
的辅助函数模板。编写自己的函数模板的细节将在本章后面讨论。函数模板一直支持基于传递给函数模板的参数自动推导模板参数。因此,make_pair()
能够根据传递给它的值自动推导出模板类型参数。例如,编译器为以下调用推导出 pair<int, double>
:
auto pair2 { make_pair(1, 2.3) };
使用类模板参数推导(CTAD),不再需要这样的辅助函数模板。编译器现在会根据传递给构造函数的参数自动推导出模板类型参数。对于 pair
类模板,您可以简单地编写以下代码:
pair pair3 { 1, 2.3 }; // pair3 的类型为 pair<int, double>
当然,这仅在类模板的所有模板参数要么具有默认值,要么用作构造函数中的参数,从而可以推导出来时才有效。请注意,CTAD 要求有一个初始化器才能工作。以下是非法的:
pair pair4;
许多标准库类支持 CTAD,例如 vector
、array
等。
注意:这种类型推导对
std::unique_ptr
和shared_ptr
无效。您向它们的构造函数传递T*
,这意味着编译器必须在推导<T>
或<T[]>
之间选择,如果选错了就会很危险。因此,请记住,对于unique_ptr
和shared_ptr
,您需要继续使用make_unique()
和make_shared()
。
用户定义的推导指南
您也可以编写自己的用户定义推导指南来帮助编译器。这些指南允许您编写模板参数如何被推导的规则。这是一个高级主题,所以不会详细讨论,但会给出一个示例来展示它们的强大功能。假设您有以下 SpreadsheetCell
类模板:
template <typename T>
class SpreadsheetCell {
public:SpreadsheetCell(T t) : m_content { move(t) } { }const T& getContent() const { return m_content; }private:T m_content;
};
使用自动模板参数推导,您可以创建一个 std::string
类型的 SpreadsheetCell
:
string myString { "Hello World!" };
SpreadsheetCell cell { myString };
然而,如果您将 const char*
传递给 SpreadsheetCell
构造函数,则类型 T
被推导为 const char*
,这不是您想要的!您可以创建以下用户定义的推导指南,当向构造函数传递 const char*
作为参数时,使其将 T
推导为 std::string
:
SpreadsheetCell(const char*) -> SpreadsheetCell<std::string>;
这个指南必须在类定义
之外但在与 SpreadsheetCell
类相同的命名空间内定义。通用语法如下。explicit
关键字是可选的,其行为与构造函数的 explicit
相同。通常,这样的推导指南也是模板。
explicit TemplateName(Parameters) -> DeducedTemplate;
相关文章:
掌握C++模板的艺术:类型参数、默认值和自动推导
掌握C模板的艺术:类型参数、默认值和自动推导 模板参数 类型模板参数 在 Grid 示例中,Grid 模板有一个模板参数:存储在网格中的类型。编写类模板时,您需要在尖括号内指定参数列表,例如: template <typename T&g…...

Unity_使用FairyGUI搭建登录页面
Unity_使用FairyGUI搭建登录页面 1. 使用FairyGUI准备一个UI界面,例如:以下登录 2. 发布导出(发布路径设置为Unity的Asset下任何路径) 3. Unity编辑器安装FairyGUI包资源(在资源商店找见并存储为我的资源,…...

百岁时代即将来临,原知因成为消费新潮流
什么叫长寿时代?泰康保险首席执行官陈东升指出:长寿时代,就是百岁人生即将来临,人人带病长期生存。而在这个时代,人类最大的变化在于“生命尺度的改变”,比如过去20岁是年轻人,40岁中年人,60岁…...

16:00的面试,16:07就出来了,问的问题过于变态了。。。
从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到六月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40…...
VUE宝典之el-dialog使用
文章目录 🍁前言🍁el-dialog简介🍁el-dialog属性🍁el-dialog示例🍁父子组件值传递示例 🍁前言 el-dialog是Element UI库中的一个重要组件,用于在Vue应用程序中创建弹出框。它提供了一组实用的属…...
Cocos Creator:坐标系
Cocos Creator:坐标系 坐标系节点位置坐标转换v3.8 实现原理(不想了解可以直接跳过)简单示例:(干货or解决方案在这里!) 锚点缩放和旋转 总结心得 在 Cocos Creator 3.8 中,节点坐标系…...

logback日志框架使用
依赖引入 <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.1.7</version> </dependency> 使用logback日志框架只需要引入以上即可,(我们平时使用较多的Slf4j…...

【八】python装饰器模式
文章目录 8.1 装饰器模式简介8.2 装饰器模式作用8.3 装饰器模式构成8.3.1 装饰器模式包含以下几个核心角色:8.3.2 UML类图 8.4 装饰器模式python代码实现8.4.1 基本装饰器的使用8.4.2 多个装饰器的执行顺序8.4.3 带返回值的装饰器的使用8.4.4 装饰器模式-关联类模式…...

Unity-小工具-LookAt
Unity-小工具-LookAt 🥙介绍 🥙介绍 💡通过扩展方法调用 gameObject.LookAtTarget,让物体转向目标位置 💡gameObject.StopLookat 停止更新 💡可以在调用时传入自动停止标记,等转向目标位置后自…...

TCP实现一对一聊天
一,创建类 二,类 1.ChatSocketServer类 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Sca…...

全面高压化与全面超快充,破解新能源汽车的时代难题
是什么让新能源车主感到疲惫与焦虑?是什么阻挡更多消费者选择新能源汽车?我们在身边进行一个简单的调查就会发现,问题的答案非常一致:充电。 充电难,充电慢的难题,始终是困扰新能源汽车产业发展,…...

02 CSS基础入门
文章目录 一、CSS介绍1. 简介2. 相关网站3. HTML引入方式 二、选择器1. 标签选择器2. 类选择器3. ID选择器4. 群组选择器 四、样式1. 字体样式2. 文本样式3. 边框样式4. 表格样式 五、模型和布局1. 盒子模型2. 网页布局 一、CSS介绍 1. 简介 CSS主要用于控制网页的外观&#…...
MyBatis框架中的5种设计模式总结
前言 MyBatis框架中使用的5种设计模式分别是:1、建造者模式(生成器模式)。2、工厂模式。3、单例模式。4、代理模式。5、适配器模式。 1、建造者模式(生成器模式) 在MyBatis环境的初始化过程中,SqlSessio…...
ffmpeg相关命令
视频转码 dav转化为mp4格式 ffmpeg -i 2021-08-10.dav -codec copy 11.mp4二进制文件转为mp4格式 // -c:v 指定视频流编码器,不指定编码会默认用mp4这种容器的默认音视频编码进入编码 // copy:不重新编码直接copy源视频流ffmpeg -i 1701687125-4fc72a…...

锂电3V升12V1A升压芯片WT3209
锂电3V升12V1A升压芯片WT3209 WT3209是一款高功率密度全集成BOOST升压转换器,具备高效能解决方案。3V升12V1A,5V升12V1A WT3209内部集成的功率MOSFET管导通电阻为上管13mΩ和下管11mΩ,具备2A开关电流能力,并且能够提供高达12.6V的输出电压。…...

Unity 置顶OpenFileDialog文件选择框
置顶文件选择框 🌭处理前🥙处理后 🌭处理前 🥙处理后 解决方案...

oomall课堂笔记
一、项目分层结构介绍 controller层(控制器层): 作用:负责输出和输入,接收前端数据,把结果返回给前端。 1.处理用户请求,接收用户参数 2.调用service层处理业务,返回响应 servi…...

Qt6.5类库实例大全:QFrame
哈喽大家好,我是20YC小二!欢迎扫码关注公众号,现在可免费领取《C程序员》在线视频教程哦! ~下面开始今天的分享内容~ 1. QFrame介绍 QFrame是Qt框架中的一个框架控件类,主要用于在图形用户界面(GUI)中创建框架&#…...

Java 数据结构篇-用数组、堆实现优先级队列
🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 优先级队列说明 2.0 用数组实现优先级队列 3.0 无序数组实现优先级队列 3.1 无序数组实现优先级队列 - 入队列 offer(E value) 3.2 无序数组实现优先级队列 - 出…...
Reactor模型
目录 1.Reactor模型是什么2.Reactor 模型应用场景3.使用 Reactor 模型的软件4.Reactor 模型 与 Actor 模型 的关系 本文主要介绍Reactor模型基本概念以及应用场景。 1.Reactor模型是什么 Reactor模型是一种事件驱动的设计模式,用于处理服务请求,它是由…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...