现代C++实战30讲
吴咏炜
前 Intel 资深软件架构师
立即订阅
3747 人已学习
课程目录
已更新 18 讲 / 共 30 讲
0/4登录后,你可以任选4讲全文学习。
课前必读 (2讲)
开篇词 | C++这么难,为什么我们还要用C++?
免费
课前必读 | 有关术语发音及环境要求
基础篇 (9讲)
01 | 堆、栈、RAII:C++里该如何管理资源?
02 | 自己动手,实现C++的智能指针
03 | 右值和移动究竟解决了什么问题?
04 | 容器汇编 I:比较简单的若干容器
05 | 容器汇编 II:需要函数对象的容器
06 | 异常:用还是不用,这是个问题
07 | 迭代器和好用的新for循环
08 | 易用性改进 I:自动类型推断和初始化
09 | 易用性改进 II:字面量、静态断言和成员函数说明符
提高篇 (7讲)
10 | 到底应不应该返回对象?
11 | Unicode:进入多文字支持的世界
12 | 编译期多态:泛型编程和模板入门
13 | 编译期能做些什么?一个完整的计算世界
14 | SFINAE:不是错误的替换失败是怎么回事?
15 | constexpr:一个常态的世界
16 | 函数对象和lambda:进入函数式编程
现代C++实战30讲
登录|注册

14 | SFINAE:不是错误的替换失败是怎么回事?

吴咏炜 2019-12-27
你好,我是吴咏炜。
我们已经连续讲了两讲模板和编译期编程了。今天我们还是继续这个话题,讲的内容是模板里的一个特殊概念——替换失败非错(substituion failure is not an error),英文简称为 SFINAE。

函数模板的重载决议

我们之前已经讨论了不少模板特化。我们今天来着重看一个函数模板的情况。当一个函数名称和某个函数模板名称匹配时,重载决议过程大致如下:
根据名称找出所有适用的函数和函数模板
对于适用的函数模板,要根据实际情况对模板形参进行替换;替换过程中如果发生错误,这个模板会被丢弃
在上面两步生成的可行函数集合中,编译器会寻找一个最佳匹配,产生对该函数的调用
如果没有找到最佳匹配,或者找到多个匹配程度相当的函数,则编译器需要报错
我们还是来看一个具体的例子(改编自参考资料 [1])。虽然这例子不那么实用,但还是比较简单,能够初步说明一下。
#include <stdio.h>
struct Test {
typedef int foo;
};
template <typename T>
void f(typename T::foo)
{
puts("1");
}
template <typename T>
void f(T)
{
puts("2");
}
int main()
{
f<Test>(10);
f<int>(10);
}
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《现代C++实战30讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(5)

  • 三味
    emmmm....
    这一节内容如果是半年前看到,应该能节省我好多时间去写序列化,真是我实实在在的需求啊!
    我自己在写数据序列化为json文本的时候,就遇到了这样头疼的问题:如何根据类型,去调用对应的函数。
    如果是简单的int,bool,float,直接特化就好了。
    如果是自定义的结构体呢?我的做法就是判断自定义结构体中是否有serializable和deserializable函数,就用到了文中最开始的方法判断。
    然而那会儿我写得还是太简单粗暴,在代码中用的是if去判断,对于不支持的类型,直接报错,并不能做到忽略。
    看了本文之后,真是受益颇多啊!留言于此,告诉大家,别以为用不到这些内容,都是实实在在的干货!

    作者回复: 我喜欢这样的留言。哈哈,写专栏就是希望能给大家帮助的。😇

    2019-12-30
    2
  • 李亮亮

    template <typename T,
              typename = void_t<>>
    struct has_reserve : false_type {};
    这里的冒号是什么语法?

    作者回复: 继承啊。

    2019-12-30
    1
  • 总统老唐
    吴老师,关于这一课,有 3 个问题
    1,在最开始定义 has_reserve 类时,两个 reserve 模板函数实际上只是声明了,但是并没有真正的函数体,而最后的 value 成员实际上是用 nullptr 调用了 reserve 函数,这就相当于调用一个没有只有声明没有定义的函数,为什么没有报错?
    2,关于模板函数的调用
    假设有如下模板
    template <typename T1, typename T2>
    int add(T1 a, T2 b);
    既可以add<int, double>(1, 2.5)调用,也可以add(1, 2.5)调用,两者的差别是不是第一种方式相当于先声明了一个特化版本,在用这个特化版本来调用,后一种方式是编译器自行推断?但若是没有定义对应的特化版本,第一种方式和第二种方式是不是完全没有区别?
    3,在 void_t 的部分,模板定义时,第二个参数是这样写的:typename = void_t<>, 我试了一下,直接写成 typename = void,也是可以的,你采用这种写法是有什么特殊考虑吗?

    作者回复: 1. 一个函数没有真正被调用,代码里就不会产生对它的引用,链接没有也就不会出问题。

    2. 不是特化,而是自动推断后进行自动实例化。特化是需要有能看得到的特化定义的。

    3. 主要是和下面的定义对称。因为这儿的类型不实际使用,写任何的合法类型都是可以的。

    2019-12-29
  • 禾桃

    template <typename T, typename = void_t<>>
    struct has_reserve : false_type {};

    template <typename T>
    struct has_reserve<T, void_t<decltype(declval<T&>().reserve(1U))>> : true_type {};

    declval().reserve(1U) 用来测试 C& 类型的对象是不是可以拿 1U 作为参数来调用 reserve 成员函数

    请问
    - 如果是, decltype(declval<T&>().reserve(1U))> 返回的是void,这个好理解,因为void_t会把任何数目(包括零个)的类型转换为类型void
    - 如果不是, 编译器看到decltype(declval<T&>().reserve(1U))> 会做什么?
                      然后编译器看到void_t<decltype(declval<T&>().reserve(1U))> 又会做什么?

    作者回复: 不是说了吗,把任意类型映射到void。任意类型哦……只要表达式合法就行。

    2019-12-27
    2
  • 禾桃
    请问有编译器本身什么工具或者日志模式,可以显示模版实例化的过程?

    作者回复: 这倒不知道有。如果失败了,输出错误信息里可以找到提示的。成功了的话,只能靠往代码里插调试语句了,可以是运行时日志或static_assert。

    2019-12-27
收起评论
5
返回
顶部