C++模板 - 16(SFINAE) c++模板函数
liebian365 2024-10-23 13:43 16 浏览 0 评论
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了。
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)