掌握C++模板的艺术:类型参数、默认值和自动推导

掌握C++模板的艺术:类型参数、默认值和自动推导

模板参数

类型模板参数

Grid 示例中,Grid 模板有一个模板参数:存储在网格中的类型。编写类模板时,您需要在尖括号内指定参数列表,例如:

template <typename T>

这个参数列表类似于函数或方法中的参数列表。与函数和方法一样,你可以编写具有任意多个模板参数的类。此外,这些参数不必是类型,它们可以有默认值。

非类型模板参数

非类型参数是普通参数,如整数和指针——这类参数你可能已经在函数和方法中很熟悉了。然而,非类型模板参数只能是整型(charintlong 等)、枚举类型、指针、引用、std::nullptr_tautoauto&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_tconstexpr 函数,你可以用它来初始化高度模板参数:

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 {// 其余部分与之前版本相同
};

在方法定义的模板规范中,您不需要为 TWIDTHHEIGHT 指定默认值。例如,这是 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,例如 vectorarray 等。

注意:这种类型推导对 std::unique_ptrshared_ptr 无效。您向它们的构造函数传递 T*,这意味着编译器必须在推导 <T><T[]> 之间选择,如果选错了就会很危险。因此,请记住,对于 unique_ptrshared_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;

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

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

相关文章

【人工智能Ⅰ】实验8:DBSCAN聚类实验

实验8 DBSCAN聚类实验 一、实验目的 学习DBSCAN算法基本原理&#xff0c;掌握算法针对不同形式数据如何进行模型输入&#xff0c;并结合可视化工具对最终聚类结果开展分析。 二、实验内容 1&#xff1a;使用DBSCAN算法对iris数据集进行聚类算法应用。 2&#xff1a;使用DBS…

centos7安装python3.11

1 安装依赖 sudo yum -y updatesudo yum -y install openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel psmisc libffi-devel -ysudo yum -y groupinstall "Development Tools"2 安装必需的 openssl > 1.1.1 wget https://www.ope…

macOS Sonoma 14.2RC(23C63)发布

系统介绍 黑果魏叔12 月 6 日消息&#xff0c;苹果今日向 Mac 电脑用户推送了 macOS 14.2 RC更新&#xff08;内部版本号&#xff1a;2323C633&#xff09;&#xff0c;本次更新距离上次发布隔了 49 天。 预计正式版会在下周到来。届时用户可以打开“设置”->“通用”->…

ATFX汇市:美联储12月利率决议来袭,或将连续第三次暂停加息

ATFX汇市&#xff1a;明日3:00&#xff0c;美联储将公布12月份利率决议结果&#xff0c;市场普遍预期联邦基金利率上限将维持在5.5%不变&#xff0c;美联储将连续第三次暂停加息。上周五公布的11月非农就业报告显示&#xff0c;新增非农就业人口19.9万人&#xff0c;远高于前值…

windows禁用系统更新

1.在winr运行框中输入services.msc&#xff0c;打开windows服务窗口。 services.msc 2.在服务窗口中&#xff0c;我们找到Windows update选项&#xff0c;如下图所示&#xff1a; 3.双击windows update服务&#xff0c;我们把启动类型改为禁用&#xff0c;如下图所示&#xff…

图片怎么去文字水印?分享三个简单无痕方法

图片怎么去文字水印&#xff1f;大家在遇到好看的图片时&#xff0c;是否曾想过将其用作自己的头像&#xff1f;然而&#xff0c;很多时候从网上保存的图片会带有平台或作者的水印&#xff0c;这会严重影响头像的整体视觉效果&#xff0c;导致我们不得不放弃使用这张图片&#…

代驾系统开发:驶向未来的智能交通服务

随着科技的迅速发展&#xff0c;代驾系统的开发成为改善出行体验和提升交通服务智能化的重要一环。本文将聚焦于代驾系统开发的技术创新&#xff0c;为读者呈现其中涉及的一些令人振奋的技术代码。 1. 区块链技术的运用&#xff1a; 区块链技术被引入代驾系统&#xff0c;可…

11.机器人系统仿真搭建gazebo环境、仿真深度相机、雷达、RGB相机

目录 1 gazebo仿真环境搭建 1.1 直接添加内置组件创建仿真环境 1.2 urdf、gazebo、rviz的综合应用 2 ROS_control 2.1 运动控制实现流程(Gazebo) 2.1.1 已经创建完毕的机器人模型&#xff0c;编写一个单独的 xacro 文件&#xff0c;为机器人模型添加传动装置以及控制器 …

Postswigger 靶场 XSS 通关

文章目录 PostSwigger靶场XSS通关学徒&#xff1a;第一关学徒&#xff1a;第二关学徒&#xff1a;第三关学徒&#xff1a;第四关学徒&#xff1a;第五关学徒&#xff1a;第六关学徒&#xff1a;第七关学徒&#xff1a;第八关学徒&#xff1a;第九关 PostSwigger靶场XSS通关 靶…

插入排序——折半插入排序

1、简述&#xff1a; 折半插入排序&#xff08;binary insertion sort&#xff09;是对插入排序算法的一种改进&#xff0c;由于排序算法过程中&#xff0c;就是不断的依次将元素插入前面已排好序的序列中。由于前半部分为已排好序的数列&#xff0c;这样我们不用按顺序依次寻…

项目部署阿里云服务器详细流程

1.购买域名 官网 域名的作用的是为了DNS解析&#xff0c;这样被人访问的不再是一连串的IP地址&#xff0c;而是形如 www.xxx.com 这样的网站 域名购买流程较长&#xff0c;还需备案&#xff0c;所以能提前准备好&#xff0c;10块左右买个.top域名

Python—KNN分类算法

原文: https://zhuanlan.zhihu.com/p/143092725 1. 概述 KNN 可以说是最简单的分类算法之一&#xff0c;同时&#xff0c;它也是最常用的分类算法之一。注意&#xff1a;KNN 算法是有监督学习中的分类算法&#xff0c;它看起来和另一个机器学习算法 K-means 有点像&#xff0…

百安居的数字化之路:找到用户的“连接器”

从线下客流遭遇悬崖式下跌到逐步回升&#xff0c;越来越多企业认识到&#xff0c;用户消费习惯的改变已经不可逆地影响着各行各业&#xff0c;只有让组织、人员、系统、产品等元素产生新的连接和协同&#xff0c;才能不断强化企业对抗风险的能力。这个过程&#xff0c;也是数字…

LeetCode452. Minimum Number of Arrows to Burst Balloons

文章目录 一、题目二、题解 一、题目 There are some spherical balloons taped onto a flat wall that represents the XY-plane. The balloons are represented as a 2D integer array points where points[i] [xstart, xend] denotes a balloon whose horizontal diameter…

改进的A*算法的路径规划(3)

5.4实验结果与讨论 为验证本文算法的有效性&#xff0c;在模拟越野环境中完成路径规划&#xff0c;并通过仿真对比 实验验证了本文改进算法的可行性和综合性能的优越性。 5.4.1 与 传 统A*实验对比 为了验证改进A* 算法与传统A* 的优越性&#xff0c;建立了7050的栅格地图(并…

如何制作AI数字人高清模型?

数字人是什么&#xff1f;重新下一个定义&#xff1a;"把人数字化&#xff0c;以行人的职责“它是用AI技术根据你的真人形象克隆出一个数字人&#xff0c;跟真人的相似度可以达到100%以上的&#xff0c;像你的动作、表情还有声音&#xff0c;都是可以被克隆出来。克隆出来…

SpringBoot程序打包失败处理

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

Unity有限状态机

一、引言 在游戏开发中&#xff0c;经常会遇到游戏角色或实体具有多种状态&#xff0c;并且在不同状态之间需要切换的情况。例如&#xff0c;一个角色可能处于行走、奔跑、跳跃等不同的状态&#xff0c;并且根据玩家的输入或游戏逻辑&#xff0c;在这些状态之间进行切换。为了…

基于以太坊的智能合约开发Solidity(内存结构体篇)

参考教程&#xff1a;【内存、引用与持久化存储】1、内存与区块链——storage与memory原理_哔哩哔哩_bilibili 1、storage与memory&#xff1a; pragma solidity ^0.5.17;contract MemoryTest { uint z 1; //这是在合约中定义的状态变量&#xff0c;它会永久地&#xff08…