C++20 概念与约束(1)—— SFINAE

1、从模板说起

众所周知,C++在使用模板时,如果有多个模板匹配,则编译器会选择最匹配的一个模板进行实例化,这也正是模板特化和偏特化的依据。

根据上面这张图中的现象,列举下面几个示例:

1、不存在模板的情况下, foo(1.0) 中的 1.0 将进行隐式转换。

2、若存在模板,模板匹配的优先级高于隐式转换。

3、若不存在实例, 编译器将用最合适的模板(图中的特化版本)进行实例化。

4、当然,在不存在实例也不存在匹配模板的情况下,编译器才会报错。

从上述现象可知,优先级中,模板实例化 > 隐式类型转换 > 报错。而报错信息自然是找不到匹配的重载函数。看似稀松平常?但这才是重点,这里先按下不表。

2、SFINAE

1、报错类型

下图将模板第二个类型形参当作一个约束,要求传入的第一个类型中包含子类型 _requires,这样势必是通不过编译的。

这里看似错误是因为 int 和 X 类型中没有 _requires 子类而报错,但事实是否如此?看它的报错:

找不到匹配的重载函数?是不是很眼熟?而我们对比因为 int 和 X 类型中没有 _requires 子类而报错的报错信息:

所以得到结论,模板匹配中,当某个它与要求不符时,编译器会将它抛弃,之后继续寻找其他匹配的模板。当无模板可匹配时,则报匹配错误。简而言之,匹配失败并非错误,这就是SFINAE(Substitution Failure Is Not An Error)。

当然如果要强行调用这个模板也不是不行,只需要手动传入第二个参数即可。

但是正确做法是传入一个包含 _requires 子类的类型。

2、另一种匹配失败

匹配规则并不一定得在模板形参列表中进行规定,整个模板是一个整体。比如规定类型得支持某种运算。

如果不了解 std::declval ,那先将 decltype 括号中的内容当作 *T{} 即可。这三个模板,1 和 2 均是 SFINAE 。虽然模板 3 一样编译不通过,但其中存在本质差别。先看报错信息:

使用 SFINAE 的模板报错实在模板实例化时,当未发现可实例化的模板便立即报错。而第三个模板报错是在实例化之后,调用 foo3<int>() 时,编译器发现 int 类型无法解引用才进行报错。如果模板特别复杂,使用 SFINAE 规则,报错信息将尤其清晰,否则可能出现几十条错误信息。

3、C++20之前的约束

1、std::enable_if_t

用于约束模板满足某些表达式的条件,如果不满足则该模板不会被实例化。

Y 类型并不匹配其中的条件"与 int 或者 X 类型相同" ,所以 Y 类型作为模板形参必定找不到匹配模板。事实上 enable_if_t 也是利用 SFINAE 规则。

enable_if_t 中第一个模板参数是 bool 型的非类型模板形参,如果传入的表达式结果是false,则 enable_if 结构体将实例化为不含 type 版本,则 enable_if_t 对 enable_if 中的 type 取别名必然失败。

2、std::declval

使类型即使不实例化也可对类型进行实例化之后的操作。

模板的约束条件是支持同类型加法运算,因此约束条件这么写看似没问题,但是仍然找不到匹配模板。因为这种约束的写法除了要求支持同类型加法运算之外,还隐含了要求"支持无参构造"这一条件。而 X 类型因为定义了拷贝构造,默认无参构造是被弃用的,因此无法通过编译。因此便需要用到 std::declval 。

3、std::void_t

将其当作一个用于类型的花括号即可。其实作用并不大。

假如现在模板要求支持同类型加法操作,又同时只允许用 X 和 Y 类型当作类型模板形参,同时要求传入的类型模板形参中包含子类型 type 。根据之前的写法:

如果要求更多,那么需要定义更多的类型模板形参,极其麻烦。如果用 std::void_t 则:

实际上依然十分麻烦。这里先演示 C++20 的写反,之后正式介绍。

相关推荐
涛ing1 小时前
32. C 语言 安全函数( _s 尾缀)
linux·c语言·c++·vscode·算法·安全·vim
独正己身2 小时前
代码随想录day4
数据结构·c++·算法
厂太_STAB_丝针3 小时前
【自学嵌入式(8)天气时钟:天气模块开发、主函数编写】
c语言·单片机·嵌入式硬件
我不是代码教父4 小时前
[原创](Modern C++)现代C++的关键性概念: 流格式化
c++·字符串格式化·流格式化·cout格式化
利刃大大5 小时前
【回溯+剪枝】找出所有子集的异或总和再求和 && 全排列Ⅱ
c++·算法·深度优先·剪枝
charlie1145141915 小时前
从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(协议层封装)
c语言·驱动开发·单片机·学习·教程·oled
子燕若水5 小时前
mac 手工安装OpenSSL 3.4.0
c++
*TQK*5 小时前
ZZNUOJ(C/C++)基础练习1041——1050(详解版)
c语言·c++·编程知识点
ElseWhereR6 小时前
C++ 写一个简单的加减法计算器
开发语言·c++·算法
*TQK*6 小时前
ZZNUOJ(C/C++)基础练习1031——1040(详解版)
c语言·c++·编程知识点