网站首页 > 博客文章 正文
C++支持函数重载,同一个函数名,只要它的签名不一样,可以声明若干个版本(这个特性也是必须的,不然构造函数就只能有一个了)。
现在函数的重载集合中又加入了新的成员 - 函数模板,事情就变得越发有趣起来,特别是还可以对这些函数模板进行完全特化。这使得编译器在匹配函数调用时的选择有时可能会出乎你的意料。
不过我们这次讲的是另外一个话题,当编译器在尝试匹配某个函数模板时,会使用调用参数的类型来推导模板参数,这个过程中编译器可能会发现这个函数模板对于这个调用参数来说生成的代码是无效的或者非法的。这个时候编译器并不会报错,而是将这个函数模板从匹配的候选列表中剔除,这个被称为“SFINAE”(Substitution Failure Is Not An Error)。
我们可以利用这个特性,定义满足不同约束的函数模板,然后可以放心地在函数体内使用这些约束,让满足各自约束的调用参数分别去匹配相应的函数模板。
下面看个例子,实现方式比较原始,但是可以很好地解释这个特性。
template<typename T>
struct HasSerialize {
template<typename U, U u>
struct ReallyHas;
template<typename C>
static std::true_type test(ReallyHas<std::string (C::*)(), &C::serialize>*) {}
template<typename C>
static std::true_type test(ReallyHas<std::string (C::*)() const, &C::serialize>*) {}
template<typename>
static std::false_type test(...) {}
static constexpr bool value = std::is_same_v<std::true_type, decltype(test<T>(nullptr))>;
};
这个类模板的功能是检测其模板参数T是否定义有serialize成员函数。比如说我们有一个序列化对象的框架,如果对象本身定义有serialize,这样我们就可以直接使用它。这个类模板使用了3个重载的成员函数模板test来实现检查操作(这个实现比较烦琐,其实有更为简单的实现方式,这里主要是用它来解释SFINAE)。
我们先定义了一个内嵌的类模板ReallyHas,它的第一个模板参数是个类型,第二个是非类型参数,是第一个类型的某个值。前两个test基本一样(区别在于多了一个函数的const限定符),这里的约束就是类型具有serialize成员函数,其返回值为std::string,参数为空。第三个test则负责匹配那些无法满足约束的那些类型替换。
class MySerialize {
public:
std::string serialize() { return std::string{}; }
};
class MySerialize2 {
public:
std::string serialize() const { return std::string{}; }
};
class NoSerialize {};
HasSerialize<MySerialize>::value; // true
HasSerialize<MySerialize2>::value; // true
HasSerialize<NoSerialize>::value; // false
上面的例子可以看成是SFINAE的一个应用场景,用来检测类型的约束(是否有某个内嵌类型的定义,或者是否定义有某个特定的成员函数)。SFINAE在C++20之前被大量用于类型的约束检测,比较简便的使用方式是配合decltype,注意要转换为void防止逗号运算符被重载导致的检测失败(这里就不举例了,因为concept出现之后,这些使用技巧慢慢就成为历史了)。
下面看一个SFINAE真正应用的例子吧。
template<typename T, std::size_t N>
std::size_t len(T (&)[N]) {
return N;
};
template<typename T>
typename T::size_type len(const T& t) {
return t.size();
};
我们定义了一个len函数,对于某个数组,则返回数组的维度,对于其他类型,则有两个约束:该类型有内嵌类型定义size_type,且有size成员函数,其返回类型为size_type。
int a[5];
std::cout << len(a); // 5
std::cout << len("hello, world"); // 13
std::vector<int> v;
std::cout << len(v); // 0
class Foo {};
std::cout << len(Foo{}); // no type named 'size_type' in 'Foo'
我们定义了一个Foo类,len对于Foo类对象,只有第二个len可以匹配,但是它没有size_type的类型定义,这时编译器给出了错误信息。
std::size_t len(...) {
return 0;
}
这次我们添加了len的第三个版本,它适用于任何参数,但它是个最差的匹配(匹配度最低),可以看作是所有无法匹配的类型的收容站。这次len(Foo{})会匹配这个版本,返回值为0(这个就是SFINAE,这时第二个版本的len不会再抱怨没有size_type类型定义了)。
class Foo {
public:
using size_type = int;
};
后面有一天,我们修改了这个Foo的定义,添加了size_type的类型定义(但是还是没有定义size成员函数)。然后,你的程序突然就编译不过了。
std::cout << len(Foo{}); // error: no member named 'size' in 'Foo'
这次len的第二个版本会匹配成功,因为这次它的签名匹配是成功的,但是在函数体内调用size成员函数时失败了。这个时候并不会再次回到那个收容站上去,而是直接编译失败。这个区别大家需要留意:SFINAE的应用只在函数重载时的签名匹配过程中,一旦匹配成功,这个时候编译器就完成函数的重载选择了,其后的代码必须是有效的,否则不再有fallback了。
猜你喜欢
- 2024-10-12 C++核心准则T.24:用标签类或特征区分只有语义不同的概念
- 2024-10-12 用苹果发布会方式打开C++20(苹果在哪开发布会)
- 2024-10-12 C++核心准则T.25:避免互补性约束(规矩是一种约束,一种准则)
- 2024-10-12 C++核心准则T.21:为概念定义一套完整的操作
- 2024-10-12 C++核心准则T.5:结合使用泛型和面向对象技术应该增强效果
- 2024-10-12 C++经典书籍(c++相关书籍)
- 2024-10-12 C++一行代码实现任意系统函数Hook
- 2024-10-12 C++核心准则T.11:只要可能就使用标准概念
- 2024-10-12 C++核心准则T.48:如果不能用概念,用enable_if
- 2024-10-12 C++核心准则T.13:简单、单类型参数概念使用缩略记法更好
你 发表评论:
欢迎- 最近发表
-
- 给3D Slicer添加Python第三方插件库
- Python自动化——pytest常用插件详解
- Pycharm下安装MicroPython Tools插件(ESP32开发板)
- IntelliJ IDEA 2025.1.3 发布(idea 2020)
- IDEA+Continue插件+DeepSeek:开发者效率飙升的「三体组合」!
- Cursor:提升Python开发效率的必备IDE及插件安装指南
- 日本旅行时想借厕所、买香烟怎么办?便利商店里能解决大问题!
- 11天!日本史上最长黄金周来了!旅游万金句总结!
- 北川景子&DAIGO缘定1.11 召开记者会宣布结婚
- PIKO‘PPAP’ 洗脑歌登上美国告示牌
- 标签列表
-
- ifneq (61)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)