【模板进阶】std::enable_if

news2024/9/24 17:42:12

一、 SFINAE

在介绍 s t d : : e n a b l e _ i f std::enable\_if std::enable_if之前,先介绍一个概念: S F I N A E SFINAE SFINAE,全称是: S u b s t i t u t i o n   F a i l u r e   i s   n o t   a n   E r r o r Substitution\ Failure \ is \ not\ an\ Error Substitution Failure is not an Error,即“替换失败不是一个错误”。

S F I N A E SFINAE SFINAE可以看做是 C + + C++ C++语言的一种特性或模板设计中要遵循的一个重要原则,非常重要,务必要理解好。


现在,我们通过一个小样例来了解一下 S F I N A E SFINAE SFINAE,参考下方代码:


template<typename T>
typename T::size_type mydouble(const T& t) {
	return t[0] * 2;
}

比如,这里我们写了一个函数模板,返回类型是 T : : s i z e _ t y p e T::size\_type T::size_type,这就要求这个返回类型必须是个类类型,而且其中必须有个类名叫作 s i z e _ t y p e size\_type size_type

如果此时我们调用

mydouble(15);

编译器将报错:
在这里插入图片描述
显然,这里编译器并不认为 m y d o u b l e mydouble mydouble这个函数模板有问题,而是找不到匹配的模板实例。因此,这就叫作 S F I N A E SFINAE SFINAE,即“替换失败不是一个错误”。


如果我们使用正确的类类型呢:

	std::vector<int>vec;
	vec.push_back(1);
	std::cout << mydouble(vec) << "\n";

此时将编译通过,因为在 s t d : : v e c t o r std::vector std::vector中存在一个类型名 s i z e _ t y p e size\_type size_type,因此 T : : s i z e _ t y p e T::size\_type T::size_type合法。


二、 s t d : : e n a b l e _ i f std::enable\_if std::enable_if的认识与使用

2.1 s t d : : e n a b l e _ i f std::enable\_if std::enable_if的基本认识

我们先看看,在标准库中是怎么实现的 s t d : : e n a b l e _ i f std::enable\_if std::enable_if

//struct template enable_if

template<bool _Test, class _Ty = void> //泛化版本
struct enable_if {

};

template<class _Ty> //偏特化版本
struct enable_if<true, _Ty> {
	using type = _Ty;

};

首先, s t d : : e n a b l e _ i f std::enable\_if std::enable_if是一个结构体模板,有一个特化版本。
当传入的第一个 b o o l bool bool类型的参数为 t r u e true true时,将调用特化版本,在特化版本的结构体中存在一个类型为 t y p e type type

这就是基于 S F I N A E SFINAE SFINAE特性实现的一个类似于编译期间条件分支的作用。


2.2 s t d : : e n a b l e _ i f std::enable\_if std::enable_if的使用

2.2.1 基本使用

下面是一个最简单的使用,参考下方代码:

void Test2() {
	//条件为真,存在type成员
	std::enable_if<(3 > 2)>::type* mypoint1 = nullptr; //调用了偏特化版本

	//条件为假,不存在type类型成员
	std::enable_if<(3 < 2)>::type* mypoint2 = nullptr; //调用了偏特化版本

}

对于上面的第一个情况,由于 ( 3 > 2 ) = t r u e (3>2) = true (3>2)=true,因此将调用偏特化版本,而偏特化版本存在 t y p e type type类型名,因此能够顺利编译通过。

而第二种情况,由于 ( 3 < 2 ) = f a l s e (3<2)=false (3<2)=false,因此将调用泛化版本,而泛化版本中不存在这么一个 t y p e type type类型名,因此无法通过编译。


2.2.2 用于函数模板

由于 s t d : : e n a b l e _ i f < > : : t y p e std::enable\_if<>::type std::enable_if<>::type是一个类型,因此它可以作为返回值,通过条件来判断是否存在这么一个返回值类型。 参考下方代码:

//std::enable_if用于函数模板中
template<typename T>
typename std::enable_if < (sizeof(T) > 2) > ::type funceb() {
	std::cout << "类型大小大于2个字节,调用函数模板!\n";
}

void Test3() {
	funceb<int>(); //成功

	funceb<char>(); //失败,char为一个字节
}

这里 s i z e o f ( ) sizeof() sizeof()可以在编译期间确定,因此我们在调用模板的时候就能知道类型的大小了,也就可以知道是否可以调用到泛化版本还是偏特化版本了。

显然, c h a r char char类型大小为 1 1 1字节,所以无法实例化出存在 t y p e type type的模板,因此就会编译失败,而 i n t int int类型是 4 4 4字节,因此是可以的。


s t d : : e n a b l e _ i f std::enable\_if std::enable_if用于函数模板的时候,也可以存在形参。参考下方代码:

//也可以有参数
template<typename T>
std::enable_if_t<(sizeof(T) == 4), T> funceb3(T&& t) {
	t++;
	return t;
}

void Test5() {
	int t = funceb3(10);

	std::cout << t << "\n";
}

注意写法,这个 T T T是放在第一个 b o o l bool bool类型之后的,就像前面代码所示,默认为 v o i d void void

在这里插入图片描述


2.2.3 类型简写

s t d : : e n a b l e _ i f std::enable\_if std::enable_if可以通过类型别名来简化代码,而标准库为我们实现了这个方法,我们可以通过 s t d : : e n a b l e _ i f _ t std::enable\_if\_t std::enable_if_t来作为其的别名。 参考下方代码:

//泛化版本
template<bool _Test, class _Ty = void> //泛化版本
struct enable_if {

};

//偏特化版本
template<bool _Test, class _Ty = void>
using enable_if_t = typename enable_if<_Test, _Ty>::type;

由于 t y p e n a m e e n a b l e _ i f < _ T e s t , _ T y > : : t y p e typename enable\_if<\_Test,\_Ty>::type typenameenable_if<_Test,_Ty>::type是一个类型,所以使用 u s i n g using using来取别名是可以的。

因此,我们可以这样简写:

//std::enable_if的简写
template<typename T>
std::enable_if_t<(sizeof(T) > 2)> funceb2() {

}

2.2.4 用于类模板中

在之前,我们遇到了一个问题:在拷贝构造的时候,在类中拷贝构造函数没有被调用,而是转而调用构造函数模板,那么这个问题在这里就可以使用 s t d : : e n a b l e _ i f std::enable\_if std::enable_if来解决,回顾问题:完美转发
这里,参考下方代码:

/使用std::enable_if解决拷贝构造函数无法被调用

class Human {
	//使用别名来简化代码
	template<typename T>
	using StrProType = std::enable_if_t<std::is_convertible<T, std::string>::value>;
private:
	std::string m_name;
public:
	//完美转发构造函数模板
	template<typename T, typename U = StrProType<T>>
	//如果能隐式转换为string类型就可以构造

	Human(T&& tmpname) :m_name(std::forward<T>(tmpname)) {
		std::cout << "Human(T&& tmpname)执行了\n";
	}

	//拷贝构造函数
	Human(const Human& th) :m_name(th.m_name) {
		std::cout << "Human(Human const&th)执行了\n";
	}

	Human(Human&& th) :m_name(std::move(th.m_name)) {
		std::cout << "Human(Human && th)执行了\n";
	}

};

我们引入的新的一个模板 s t d : : i s _ c o n v e r t i b l e std::is\_convertible std::is_convertible,这是一个用于判断两个类型是否能隐式转换的函数模板,返回一个 b o o l bool bool类型。下面介绍一下简单的使用方法:

void Test6() {
	//判断是否能隐式转换
	std::cout << "std::string -> double: " << std::is_convertible<std::string, double>::value << "\n";
	std::cout << "char -> int: " << std::is_convertible<char, int>::value << "\n";
	std::cout << "const char* -> std::string: " << std::is_convertible<const char*, std::string>::value << "\n";
}

运行结果:

在这里插入图片描述


下面我们来看我们对构造函数模板的改造:

using StrProType = std::enable_if_t<std::is_convertible<T, std::string>::value>;

//完美转发构造函数模板
template<typename T, typename U = StrProType<T>>
//如果能隐式转换为string类型就可以构造

Human(T&& tmpname) :m_name(std::forward<T>(tmpname)) {
	std::cout << "Human(T&& tmpname)执行了\n";
}

为了防止代码过长,我们使用了类型别名来简化代码。

我们利用了 s t d : : e n a b l e _ i f _ t std::enable\_if\_t std::enable_if_t,然后传入 s t d : : i s _ c o n v e r t i b l e std::is\_convertible std::is_convertible,传入当前的类型和 s t d : : s t r i n g std::string std::string类型,如果当前类型能与 s t d : : s t r i n g std::string std::string类型隐式转换,那么就会返回 t r u e true true,因此 s t d : : e n a b l e _ i f _ t std::enable\_if\_t std::enable_if_t就能被实例化出,因此就会调用构造函数模板。反之,将调用拷贝构造函数模板。


再一次调用代码:

void Test7() {
	Human h1("ZhangSan");

	Human h2(h1);

	const char* str = "LiSi";
	Human h3(str);
}

这一次,成功调用了拷贝构造函数:

在这里插入图片描述

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

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

相关文章

【java21】java21新特性之JavaDoc中支持代码片段

在Java18之前&#xff0c;已经支持在JavaDoc中引入代码片段&#xff0c;这样可以在某些场景下更好的展示描述信息&#xff0c;但是之前的支持功能有限&#xff0c;比如我想高亮代码片段中的某一段代码是无能为力的。现在Java18优化了这个问题&#xff0c;增加了snippet来引入更…

短视频矩阵管理系统贴牌 源码开发

抖音账号矩阵的开发核心维度包括&#xff1a; 多账号管理开发维度&#xff1a;通过运用不同类型的账号矩阵&#xff0c;可以实现统一且便捷的管理。目前&#xff0c;矩阵系统支持管理抖音、快手、视频号,b站的账号&#xff0c;未来计划加入小红书,tk等等的账号管理。 矩阵账号…

如何编写高质量的用户故事

本文详细介绍了如何在敏捷开发过程中编写高质量用户故事&#xff08;User Story&#xff09;&#xff0c;包括用户故事的定义、结构、撰写技巧以及如何与产品待办列表&#xff08;Product Backlog&#xff09;中的其他工作项&#xff08;PBI&#xff09;相结合&#xff0c;以提…

【Elasticsearch系列廿二】特殊参数

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

解释器模式原理剖析和Spring中的应用

解释器模式原理剖析和Spring中的应用 解释器模式 是一种行为型设计模式&#xff0c;它定义了一种语言的文法表示&#xff0c;并提供了一个解释器来处理该文法的表达式。解释器模式可以用于构建语法解释器&#xff0c;例如计算器、简单编程语言的解释器等。 核心思想&#xff1a…

成功使用DDNS动态域名访问我的群晖NAS(TP-link路由器)

当NAS设备部署在动态IP环境中&#xff08;如家庭或小型办公室宽带&#xff09;&#xff0c;远程访问常常受到IP地址频繁变动的困扰。为了解决这一问题&#xff0c;结合神卓互联NAS公网助手提供的DDNS&#xff08;动态域名服务&#xff09;功能&#xff0c;我们可以轻松实现通过…

蓝牙、WiFi、2.4G、Zigbee、LoRa、NB-IoT的区别与应用场景

在现代科技的推动下&#xff0c;无线通信技术已经成为我们生活中不可或缺的一部分。从智能家居到工业自动化&#xff0c;从远程监控到环境传感&#xff0c;每一种技术都有其独特的优势和应用场景。今天&#xff0c;我们将深入探讨六种主流的无线通信技术——蓝牙、WiFi、2.4G、…

基于vue框架的大参林药品信息管理系统的设计与实现8b4gt(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,药品分类,药品信息,医生 开题报告内容 基于Vue框架的大参林药品信息管理系统的设计与实现开题报告 一、引言 随着医疗健康行业的快速发展和信息化浪潮的推进&#xff0c;药品信息管理已成为提升医疗服务效率、保障患者用药安全、…

Activiti7《第九式:破气式》——流畅驱动工作流进程。面试题大全

冲冲冲&#xff01;开干 这篇文章将分为九个篇章&#xff0c;带你逐步掌握工作流的核心知识。“破气式”&#xff0c;代表着工作流中的 无形之力&#xff0c;它是贯穿整个流程的 关键驱动 不知不觉已经到了独孤九剑最后一式了&#xff0c;我相信到这里之后各位都已经出神入化…

状态模式原理剖析

《状态模式原理剖析》 状态模式&#xff08;State Pattern&#xff09; 是一种行为设计模式&#xff0c;它允许对象在其内部状态改变时改变其行为。换句话说&#xff0c;当对象状态发生变化时&#xff0c;它的行为也会随之变化。 核心思想&#xff1a; 状态模式将对象的不同状…

爬虫逆向学习(七):补环境动态生成某数四代后缀MmEwMD

声明&#xff1a;本篇文章内容是整理并分享在学习网上各位大佬的优秀知识后的实战与踩坑记录 前言 这篇文章主要是研究如何动态生成后缀参数MmEwMD的&#xff0c;它是在文章爬虫逆向学习(六)&#xff1a;补环境过某数四代的基础上进行研究的&#xff0c;代码也是在它基础上增…

华为HarmonyOS灵活高效的消息推送服务(Push Kit) -- 10 推送实况窗消息

场景介绍 实况窗是一种帮助用户聚焦正在进行的任务&#xff0c;方便快速查看和即时处理的通知形态。有关实况窗简介、权限申请、开放场景、设计规范等说明&#xff0c;请参见Live View Kit简介。 通过Push Kit发送的实况窗消息支持三种操作类型&#xff0c;分别是&#xff1a…

【全新课程】正点原子《基于GD32 ARM32单片机项目实战入门》培训课程上线!

正点原子《ESP32物联网项目实战》全新培训课程上线啦&#xff01;正点原子工程师手把手教你学&#xff01;彻底解决ARM32单片机项目入门难的问题&#xff01; 一、课程介绍 本课程专为ARM32单片机的入门学习者设计&#xff0c;涵盖了环境搭建、编程软件使用、模块基础驱动和多…

矩阵的逆怎么算?逆矩阵公式来了(附逆矩阵计算器)

大家好&#xff0c;这里是效率办公指南&#xff01; &#x1f4da; 在线性代数中&#xff0c;逆矩阵是一个非常重要的概念。一个方阵如果存在逆矩阵&#xff0c;意味着该矩阵是可逆的&#xff0c;或者说是非奇异的。逆矩阵在解决线性方程组、计算矩阵的方根等方面有着广泛的应…

利用Accelerate()进行pytorch的多GPU加速

简介 官方Github&#xff1a;https://github.com/huggingface/accelerate Accelerate 是为喜欢编写PyTorch模型的训练循环但不愿意编写和维护使用多GPU/TPU/fp16所需的样板代码的PyTorch用户创建的。 它可以仅加速与多 GPU/TPU/fp16 相关的样板代码&#xff0c;并保持其余代…

Pyspark dataframe基本内置方法(5)

文章目录 Pyspark sql DataFrame相关文章toDF 设置新列名toJSON row对象转换json字符串toLocallterator 获取迭代器toPandas 转换python dataframetransform dataframe转换union unionALL 并集不去重&#xff08;按列顺序&#xff09;unionByName 并集不去重&#xff08;按列名…

jenkins声明式流水线语法详解

最基本的语法包含 pipeline&#xff1a;所有有效的声明式流水线必须包含在一个 pipeline 块中stages&#xff1a;包含一系列一个或多个stage指令stage&#xff1a;stage包含在stages中进行&#xff0c;比如某个阶段steps&#xff1a;在阶段中具体得执行操作&#xff0c;一个或…

提升工作效率神器

这五款软件让你事半功倍 在当今快节奏的社会中&#xff0c;提高工作效率成为了每个人追求的目标。而在这个数字化时代&#xff0c;选择对的软件工具无疑是提高效率的关键。今天&#xff0c;我为大家推荐五款优秀的工作效率软件&#xff0c;帮助你在工作中事半功倍。 1、亿可达…

15个 Jenkins 面试题

Jenkins 已成为持续集成和持续部署 (CI/CD) 流程中使用最广泛的自动化服务器之一。凭借其强大的功能和广泛的插件生态系统&#xff0c;Jenkins 已成为全球软件开发团队的首选工具。如果您正在准备 Jenkins 面试&#xff0c;那么精通其概念、架构和最佳实践至关重要。 为了帮助…

1.3 MySql的用户管理

一、下载Mysql客户端 下载navicat:Navicat 中国 | 支持 MySQL、Redis、MariaDB、MongoDB、SQL Server、SQLite、Oracle 和 PostgreSQL 的数据库管理 二、安装Navicat 三、创建数据库 创建一个数据库的连接吧&#xff0c;因为这个界面儿是图形界面儿&#xff0c;所以我们创建…