C++模板梳理

目录

函数模板

类模板

变量模板

模板全特化

模板偏特化

模板显式实例化解决文件分离问题

折叠表达式

模板的二阶段编译

待决名(dependent name)

SFINAE

概念与约束


函数模板

函数模板不是函数,只有实例化的函数模板,编译器才能生成实际的函数定义,不过在很多时候,它看起来就像普通函数一样。

示例:

下面是一个函数模板,返回两个对象中较大的那个:

template<typename T>
T max(T a,T b){return a > b ? a : b;
}

这里其实对T有几个要求,1:有>运算符,比如内置的int,double; 2:返回了一个T,即要求T是可以移动或复制的。 如果函数模板实参不满足以上要求,则匹配不到此模板。

C++17 之前,类型 T 必须是可复制或移动才能传递参数。C++17 以后,即使复制构造函数和移动构造函数都无效,因为 C++17 强制的复制消除的存在,也可以传递临时纯右值。

使用模板

template<typename T>
T max(T a, T b) {return a > b ? a : b;
}int main(){int a{ 1 };int b{ 2 };max(a, b);          // 函数模板 max 被推导为 max<int>max<double>(a, b);  // 传递模板类型实参,函数模板 max 为 max<double>
}

模板函数可手动指定模板形参类型,也可以让编译器推导,即模板参数推导(template argument deduction),c++11支持函数模板参数推导,但是类模板参数推导要到c++17才支持。

对于编译无法推导的场景,可手动指定,如:

max<double>(1, 1.2);           
max<std::string>("luse"s, "乐");

但是 std::string 没有办法如此操作,编译器会报:

<source>:31:4: error: call of overloaded 'max(std::string, std::string)' is ambiguous

31 | max(string("luse"), string("乐"));

| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

<source>:23:3: note: candidate: 'T max(const T&, const T&) [with T = std::__cxx11::basic_string<char>]'

23 | T max(const T& a, const T& b) {

| ^~~

/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/stl_algobase.h:257:5: note: candidate: 'constexpr const _Tp& std::max(const _Tp&, const _Tp&) [with _Tp = __cxx11::basic_string<char>]'

257 | max(const _Tp& __a, const _Tp& __b)

即函数有二义性,这是因为自己所编写的max函数和标准库的max函数冲突了,但是int double实例化max函数并不会。原因是string在std命名空间,标准库的max函数也在std命名空间,虽然我们没有使用 std::,但是根据 C++ 的查找规则,(实参依赖查找)ADL,依然可以查找到。

那么我们如何解决呢?很简单,进行有限定名字查找,即使用 :: 或 std:: 说明,你到底要调用 “全局作用域”的 max,还是 std 命名空间中的 max。

::max(string("luse"), std::string("乐"));

有默认实参的模板类型形参

就如同函数形参可以有默认值一样,模板形参也可以有默认值。

template<typename T = int>
void f();f();            // 默认为 f<int>
f<double>();    // 显式指明为 f<double>using namespace std::string_literals;template<typename T1,typename T2,typename RT = decltype(true ? T1{} : T2{}) >RT max(const T1& a, const T2& b) { // RT 是 std::stringreturn a > b ? a : b;
}int main(){auto ret = ::max("1", "2"s);std::cout << ret << '\n';
}

让 max 函数模板接受两个参数的时候不需要再是相同类型,那么这自然而然就会引入另一个问题了,如何确定返回类型?

typename RT = decltype(true ? T1{} : T2{})

这是一个三目运算符表达式。然后外面使用了 decltype 获取这个表达式的类型,那么问题是,为什么是 true 呢?以及为什么需要 T1{},T2{} 这种形式?

1:我们为什么要设置为 true

其实无所谓,设置 false 也行,true 还是 false 不会影响三目表达式的类型。这涉及到了一些复杂的规则,简单的说就是三目表达式要求第二项和第三项之间能够隐式转换,然后整个表达式的类型会是 “公共”类型

比如第二项是 int 第三项是 double,三目表达式当然会是 double。

using T = decltype(true ? 1 : 1.2);
using T2 = decltype(false ? 1 : 1.2);

2:为什么需要 T1{}T2{} 这种形式?

没有办法,必须构造临时对象来写成这种形式,这里其实是不求值语境,我们只是为了写出这样一种形式,让 decltype 获取表达式的类型罢了。

模板的默认实参的和函数的默认实参大部分规则相同。

decltype(true ? T1{} : T2{}) 解决了。

事实上上面的写法都十分的丑陋与麻烦,我们可以使用 auto 简化这一切。

template<typename T,typename T2>
auto max(const T& a, const T2& b) -> decltype(true ? a : b){return a > b ? a : b;
}

这是 C++11 后置返回类型,它和我们之前用默认模板实参 RT 的区别只是稍微好看了一点吗?

不,它们的返回类型是不一样的,如果函数模板的形参是类型相同 true ? a : b 表达式的类型是 const T&;如果是 max(1, 2) 调用,那么也就是 const int&而前面的例子只是 T 即 int(前面都是用模板类型参数直接构造临时对象,而不是有实际对象,自然如此,比如 T{})。

使用 C++20 简写函数模板,我们可以直接再简化为:

decltype(auto) max(const auto& a, const auto& b)  {return a > b ? a : b;
}

效果和上面使用后置返回类型的写法完全一样;C++14 引入了两个特性:

  1. 返回类型推导(也就是函数可以直接写 auto 或 decltype(auto) 做返回类型,而不是像 C++11 那样,只是后置返回类型。

  2. decltype(auto) “如果返回类型没有使用 decltype(auto),那么推导遵循模板实参推导的规则进行”。我们上面的 max 示例如果不使用 decltype(auto),按照模板实参的推导规则,是不会有引用和 cv 限定的,就只能推导出返回 T 类型。即直接写auto会丢弃CV限定符,但decltype(auto)按decltype的规则推导类型,会保留CV限定符。

非类型模板形参

既然有”类型模板形参“,自然有非类型的,顾名思义,也就是模板不接受类型,而是接受值或对象。

template<std::size_t N>
void f() { std::cout << N << '\n'; }f<100>();

非类型模板形参有众多的规则和要求,目前,你简单认为需要参数是“常量”即可。

非类型模板形参当然也可以有默认值:

template<std::size_t N = 100>
void f() { std::cout << N << '\n'; }f();     // 默认      f<100>
f<66>(); // 显式指明  f<66>

重载函数模板​

函数模板与非模板函数可以重载。

这里会涉及到非常复杂的函数重载决议,即选择到底调用哪个函数。

我们用一个简单的示例展示一部分即可:

template<typename T>
void test(T) { std::puts("template"); }void test(int) { std::puts("int"); }test(1);        // 匹配到test(int)
test(1.2);      // 匹配到模板
test("1");      // 匹配到模板
  • 通常优先选择非模板的函数。

可变参数模板

和其他语言一样,C++ 也是支持可变参数的,我们必须使用模板才能做到。

老式 C 语言的变长实参有众多弊端,参见。

同样的,它的规则同样众多繁琐,我们不会说太多,以后会用到的,我们当前还是在入门阶段。

我们提一个简单的需求:

我需要一个函数 sum,支持 sum(1,2,3.5,x,n...) 即函数 sum 支持任意类型,任意个数的参数进行调用,你应该如何实现?

首先就要引入一个东西:形参包

本节以 C++14 标准进行讲述。

模板形参包是接受零个或更多个模板实参(非类型、类型或模板)的模板形参。函数形参包是接受零个或更多个函数实参的函数形参。

template<typename...Args>
void sum(Args...args){}

这样一个函数,就可以接受任意类型的任意个数的参数调用,我们先观察一下它的语法和普通函数有什么不同。

模板中需要 typename 后跟三个点 Args,函数形参中需要用模板类型形参包后跟着三个点 再 args。

args 是函数形参包,Args 是类型形参包,它们的名字我们可以自定义。

args 里,就存储了我们传入的全部的参数,Args 中存储了我们传入的全部参数的类型。

那么问题来了,存储很简单,我们要如何把这些东西取出来使用呢?这就涉及到另一个知识:形参包展开。

void f(const char*, int, double) { puts("值"); }
void f(const char**, int*, double*) { puts("&"); }template<typename...Args>
void sum(Args...args){  // const char * args0, int args1, double args2f(args...);   // 相当于 f(args0, args1, args2)f(&args...);  // 相当于 f(&args0, &args1, &args2)
}int main() {sum("luse", 1, 1.2);
}

类模板

变量模板

模板全特化

模板偏特化

模板显式实例化解决文件分离问题

折叠表达式

模板的二阶段编译

待决名(dependent name)

SFINAE

概念与约束

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

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

相关文章

数据链共享:从印巴空战到工业控制的跨越性应用

摘要 本文通过对印巴空战中数据链共享发挥关键作用的分析&#xff0c;引出数据链共享在工业控制领域同样具有重大价值的观点。深入阐述 DIOS 工业控制操作系统作为工业数据链共享基础技术的特点、架构及应用优势&#xff0c;对比空战场景与工业控制场景下数据链共享的相…

巡检机器人数据处理技术的创新与实践

摘要 随着科技的飞速发展&#xff0c;巡检机器人在各行业中逐渐取代人工巡检&#xff0c;展现出高效、精准、安全等显著优势。当前&#xff0c;巡检机器人已从单纯的数据采集阶段迈向对采集数据进行深度分析的新阶段。本文探讨了巡检机器人替代人工巡检的现状及优势&#xff0c…

在 Flink + Kafka 实时数仓中,如何确保端到端的 Exactly-Once

在 Flink Kafka 构建实时数仓时&#xff0c;确保端到端的 Exactly-Once&#xff08;精确一次&#xff09; 需要从 数据消费&#xff08;Source&#xff09;、处理&#xff08;Processing&#xff09;、写入&#xff08;Sink&#xff09; 三个阶段协同设计&#xff0c;结合 Fli…

当可视化遇上 CesiumJS:突破传统,打造前沿生产配套方案

CesiumJS 技术基础介绍 CesiumJS 是一款基于 JavaScript 的开源库&#xff0c;专门用于创建动态、交互式的地理空间可视化。它利用 WebGL 技术&#xff0c;能够在网页浏览器中流畅地渲染高分辨率的三维地球和地图场景。CesiumJS 支持多种地理空间数据格式&#xff0c;包括但不…

RabbitMQ深入学习

继续上一节的学习&#xff0c;上一节学习了RabbitMQ的基本内容&#xff0c;本节学习RabbitMQ的高级特性。 RocketMQ的高级特性学习见这篇博客 目录 1.消息可靠性1.1生产者消息确认1.2消息持久化1.3消费者消息确认1.4消费失败重试机制1.5消息可靠性保证总结 2.什么是死信交换机…

Linux系统:虚拟文件系统与文件缓冲区(语言级内核级)

本节重点 初步理解一切皆文件理解文件缓冲区的分类用户级文件缓冲区与内核级文件缓冲区用户级文件缓冲区的刷新机制两级缓冲区的分层协作 一、虚拟文件系统 1.1 理解“一切皆文件” 我们都知道操作系统访问不同的外部设备&#xff08;显示器、磁盘、键盘、鼠标、网卡&#…

在c++中老是碰到string,这是什么意思?

定义一个string类型变量的引用&#xff0c;相当于给现有变量起个别名&#xff0c;与指针还是不一样的。比如string a;string& ba;这两句&#xff0c;b与a实际上是一回事&#xff0c;表示的是同一块内存。 std是系统的一个命名空间(有关命名空间可以参阅namespace_百度百科)…

Day21 奇异值分解(SVD)全面解析

一、奇异值分解概述 奇异值分解是线性代数中一个重要的矩阵分解方法&#xff0c;对于任何矩阵&#xff0c;无论是结构化数据转化成的“样本 * 特征”矩阵&#xff0c;还是天然以矩阵形式存在的图像数据&#xff0c;都能进行等价的奇异值分解&#xff08;SVD&#xff09;。 二…

akshare爬虫限制,pywencai频繁升级个人做量化,稳定数据源和券商的选择

做量化&#xff0c;数据和交易接口是策略和自动化交易的基石&#xff0c;而稳定的数据和快人一步的交易接口是个人做量化的催化剂。 之前写过一篇文章&#xff1a;个人做量化常用的数据&#xff0c;多以爬虫为主&#xff0c;最近akshare爬虫限制&#xff0c;pywencai频繁升级。…

数字签名与证书

1. 数字签名与证书 摘要算法用来实现完整性&#xff0c;能够为数据生成独一无二的“指纹”&#xff0c;常用的算法是 SHA-2&#xff1b;数字签名是私钥对摘要的加密&#xff0c;可以由公钥解密后验证&#xff0c;实现身份认证和不可否认&#xff1b;公钥的分发需要使用数字证书…

Ubuntu22.04安装显卡驱动/卸载显卡驱动

报错 今日输入nvidia-smi报错,在安装了535和550,包括560都没办法解决,但是又怕乱搞导致环境损坏,打算把显卡卸载然后重新安装系统默认推荐版本的显卡驱动 qinqin:~$ nvidia-smi Failed to initialize NVML: Driver/library version mismatch NVML library version: 560.35卸载…

Web 架构之负载均衡全解析

文章目录 一、引言二、思维导图三、负载均衡的定义与作用定义作用1. 提高可用性2. 增强性能3. 实现扩展性 四、负载均衡类型硬件负载均衡代表设备优缺点 软件负载均衡应用层负载均衡代表软件优缺点 网络层负载均衡代表软件优缺点 五、负载均衡算法轮询算法&#xff08;Round Ro…

linux下的Redis的编译安装与配置

配合做开发经常会用到redis&#xff0c;整理下编译安装配置过程&#xff0c;仅供参考&#xff01; --------------------------------------Redis的安装与配置-------------------------------------- 下载 wget https://download.redis.io/releases/redis-6.2.6.tar.gz tar…

A2A大模型协议及Java示例

A2A大模型协议概述 1. 协议作用 A2A协议旨在解决以下问题&#xff1a; 数据交换&#xff1a;不同应用程序之间的数据格式可能不一致&#xff0c;A2A协议通过定义统一的接口和数据格式解决这一问题。模型调用&#xff1a;提供标准化的接口&#xff0c;使得外部应用可以轻松调…

关键点检测--使用YOLOv8对Leeds Sports Pose(LSP)关键点检测

目录 1. Leeds Sports Pose数据集下载2. 数据集处理2.1 获取标签2.2 将图像文件和标签文件处理成YOLO能使用的格式 3. 用YOLOv8进行训练3.1 训练3.2 预测 1. Leeds Sports Pose数据集下载 从kaggle官网下载这个数据集&#xff0c;地址为link&#xff0c;下载好的数据集文件如下…

20250508在WIN10下使用移远的4G模块EC200A-CN直接上网

1、在WIN10/11下安装驱动程序&#xff1a;Quectel_Windows_USB_DriverA_Customer_V1.1.13.zip 2、使用移远的专用串口工具&#xff1a;QCOM_V1.8.2.7z QCOM_V1.8.2_win64.exe 3、配置串口UART42/COM42【移远会自动生成连续三个串口&#xff0c;最小的那一个】 AT命令&#xf…

第J7周:ResNeXt解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目标 具体实现 &#xff08;一&#xff09;环境 语言环境&#xff1a;Python 3.10 编 译 器: PyCharm 框 架: Tensorflow &#xff08;二&#xff09;具体…

C++之类和对象:初始化列表,static成员,友元,const成员 ……

目录 const成员函数&#xff1a; 前置和后置重载&#xff1a; 取地址及const取地址操作符重载&#xff1a; 初始化列表&#xff1a; explicit关键字&#xff1a; static成员&#xff1a; 友元&#xff1a; 友元函数&#xff1a; 友元类&#xff1a; 内部类&#xff1a…

uni-app 中的条件编译与跨端兼容

uni-app 为了实现一套代码编译到多个平台&#xff08;包括小程序&#xff0c;App&#xff0c;H5 等&#xff09;&#xff0c;引入了条件编译机制。 通过条件编译&#xff0c;我们可以针对不同的平台编写特定的代码&#xff0c;从而实现跨端兼容。 一、条件编译的作用 平台差异…

Linux平台下SSH 协议克隆Github远程仓库并配置密钥

目录 注意&#xff1a;先提前配置好SSH密钥&#xff0c;然后再git clone 1. 检查现有 SSH 密钥 2. 生成新的 SSH 密钥 3. 将 SSH 密钥添加到 ssh-agent 4. 将公钥添加到 GitHub 5. 测试 SSH 连接 6. 配置 Git 使用 SSH 注意&#xff1a;先提前配置好SSH密钥&#xff0c;然…