14 | SFINAE:不是错误的替换失败是怎么回事?
14 | SFINAE:不是错误的替换失败是怎么回事?
讲述:吴咏炜
时长10:32大小9.63M
函数模板的重载决议
编译期成员检测
SFINAE 模板技巧
enable_if
decltype 返回值
void_t
标签分发
静态多态的限制?
内容小结
课后思考
参考资料
赞 6
提建议
精选留言(25)
- 三味2019-12-30emmmm.... 这一节内容如果是半年前看到,应该能节省我好多时间去写序列化,真是我实实在在的需求啊! 我自己在写数据序列化为json文本的时候,就遇到了这样头疼的问题:如何根据类型,去调用对应的函数。 如果是简单的int,bool,float,直接特化就好了。 如果是自定义的结构体呢?我的做法就是判断自定义结构体中是否有serializable和deserializable函数,就用到了文中最开始的方法判断。 然而那会儿我写得还是太简单粗暴,在代码中用的是if去判断,对于不支持的类型,直接报错,并不能做到忽略。 看了本文之后,真是受益颇多啊!留言于此,告诉大家,别以为用不到这些内容,都是实实在在的干货!展开
作者回复: 我喜欢这样的留言。哈哈,写专栏就是希望能给大家帮助的。😇
29 - 禾桃2019-12-27请问有编译器本身什么工具或者日志模式,可以显示模版实例化的过程?
作者回复: 新发现一个工具,可以展示实例化的过程。你可以去看一下: https://cppinsights.io/
共 2 条评论14 - 好好虚度时光2020-01-17表示真的太烧脑了
作者回复: 这个网站也许可以帮你: https://cppinsights.io/
共 2 条评论6 - 西加加2020-05-16看完了两课之后,正儿八经的想把各种 type_traits 用起来了。
作者回复: 在合适的地方使用,记住目标是简化最终代码就行。模板代码容易让人脑袋疼是事实,限制主要用在底层的支持代码里。上层的使用代码一定要简单为好。
2 - _呱太_2020-04-09刚更新的时候看得头晕目眩,发现基础不够。回头抽空撸完了 STL 标准库 和 effective modern C++,回过头来看豁然开朗,真实受益匪浅
作者回复: 欢迎走上一个台阶。学习无止境……
3 - geek2021-03-08老师, 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 {}; 第二个是一个类特化,但我理解,第二个中void的模板参数的推导结果和第一个是一样的。那此处 更特殊 这个意思是体现在 void的模板参数推导过程(无错)吗?展开
作者回复: 不是所有类型都能满足第二个模板。
1 - 李亮亮2019-12-30template <typename T, typename = void_t<>> struct has_reserve : false_type {}; 这里的冒号是什么语法?
作者回复: 继承啊。
共 2 条评论1 - Roblaboy2022-09-29 来自江苏我这边提示 enable_if_t 不是模板C/C++(864) 是为什么,我这边用的是C++17
作者回复: 请给出详细的错误代码、编译命令行和错误信息。
- Geek_fa72262022-09-28 来自江苏auto append(C& container, T* ptr, size_t size) -> decltype(...)中的->是什么语法?
作者回复: 后置返回值声明。此处主要是利用原型的 SFINAE(函数体中的内容不参与SFINAE,失败了会真编译失败,而不是跳过当前函数)。
- HI2022-08-22 来自广东666
- Simon2022-08-01 来自广东typename = void_t<> 这个写法是什么意思?类型名是不重要的?
作者回复: 等效于 typename = void。类型参数名不重要。这样,特化的版本和通用的版本,后一项都是 void,不用调用者提供。 通用模板形式的第二个参数如果不是 void,那特化形式就永远不能匹配成功了,除非调用者手工提供第二项 void(第二项 void_t 的结果仍是 void)。
- Geek_fd78c02022-06-30请问老师这个->是什么意思,不太懂这个语法? template <typename C, typename T> auto append(C& container, T* ptr, size_t size) -> decltype( declval<C&>().reserve(1U), void())展开
作者回复: 参见 https://zh.cppreference.com/w/cpp/language/function 「尾随返回类型 - (C++11 起) 尾随返回类型,当返回类型取决于实参名时,例如 template <class T, class U> auto add(T t, U u) -> decltype(t + u);,或当返回类型复杂时,例如在 auto fpif(int)->int(*)(int) 中,尾随返回类型很有用」
- Geek_7ce0302021-12-09模板本身图灵完备但是语法极其晦涩丑陋,既然如此,为何不用Python语法代替现有模板语法,本质上是暴露编译期接口,当前用模板暴露,也可以用正常的语言暴露,让用户更轻松地控制编译过程。可能是积重难返吧。
作者回复: 没人严肃地提出这样的提案。你的想法中的复杂性恐怕你也没有认真考虑。今天就更没有意义了——因为我们可以用C++的传统方式写编译期计算的代码。继续往下看constexpr。
共 2 条评论 - ano2021-10-20老师,我有个疑问,关于这一句 静态多态的限制? “在 C 类型没有 reserve 成员函数的情况下,编译是不能通过的,会报错。这是因为 C++ 是静态类型的语言,所有的函数、名字必须在编译时被成功解析、确定” if (has_reserve<C>::value) 我的理解是,在编译时,if 语句中的 has_reserve 进行模板匹配,决议,发现那个特化的 true_type has_reserve, 产生 substitution failure, 然后就会回到那个 false_type 的 has_reserve, 最终实例化成 false_type 的 has_reserve,然后 value 是 false, 所以这个 if 语句中始终为 false,但是不应该会编译错误呀。 不知道我哪里理解的不对?展开
作者回复: 函数中的普通代码总会被编译,哪怕这个条件执行不到。 同时参考下一讲的内容。😎
- 常振华2021-10-13标签分发那里的: template void append(C& container, T* ptr, size_t size) { _append( container, ptr, size, integral_constant< bool, has_reserve<C>::value>{});} 这个integral_constant< bool, has_reserve<C>::value>{}看不明白 integral_constant是上一讲的 template <class T, T v> struct integral_constant { static const T value = v; typedef T value_type; typedef integral_constant type; }; 吧? has_reserve<C>::value的::value是啥意思?展开
作者回复: 看看下面的例子,是不是可以了? integral_constant<int, 1>::value == 1 integral_constant<int, 2>::value == 2 true_type::value == integral_constant<bool, true>::value == true
- chang2021-06-06template <typename T> struct has_reserve { struct good { char dummy; }; struct bad { char dummy[2]; }; template <class U, void (U::*)(size_t)> struct SFINAE {}; template <class U> static good reserve(SFINAE<U, &U::reserve>*); template <class U> static bad reserve(...); static const bool value = sizeof(reserve<T>(nullptr)) == sizeof(good); }; 这个貌似对标准库的vector无效?因为参数类型不匹配?展开
作者回复: 又测了一遍,用下面的语句可以输出 1,应该没问题: cout << has_reserve<vector<int>>::value;
共 4 条评论 - 乐意至极2021-04-24class GoodClass { void reserve(size_t x); }; class BadClass { }; std::cout << has_reserve<BadClass>::value << std::endl; std::cout << has_reserve<GoodClass>::value << std::endl; template <typename T> struct has_reserve { struct good { char dummy; }; struct bad { char dummy[2]; }; template <class U, void (U::*)(size_t)> struct SFINAE {}; template <class U> static good reserve(SFINAE<U, &U::reserve>*); template <class U> static bad reserve(...); static const bool value = sizeof(reserve<T>(nullptr)) == sizeof(good); }; 老师,请问has_reserve该怎么用呢?两者输出都是0啊展开
作者回复: 哦,原因实际很简单,外面(包括 has_reserve)访问不到 GoodClass::reserve——你没有标 public。
共 2 条评论 - AlphaCat2020-06-29老师,替换失败非错(substituion failure is not an error), "substituion" 掉了一个 t,应该是 Substitution
作者回复: 嗯,谢谢。已经联系编辑。
- 西加加2020-05-16利用 variant 的 std::get 方法,写一个 sfine 用于判断一个 variant 类型里是否联合了 float. 代码和问题如下: template <typename T, typename ...> struct CheckVariant { static constexpr int value = 1; }; template <typename T> struct CheckVariant<T, decltype(get<float>( declval<T>() )) > { static constexpr int value = 2; }; ... if constexpr (CheckVariant<decltype(v)>::value == 1) { cout << get<float>(v) << endl; } ... 可是发现这样的 SFINAE 不起作用,把模板特化代码改为利用 逗号表达式,让 decltype 取值就可以了: template <typename T> struct CheckVariant<T, decltype((get<float>( declval<T>() ), void())) > { static constexpr int value = 2; }; 这是为什么呢?展开
作者回复: 看起来你对模板推导有点误解,第二个式子完全没有用处。你没有在任何地方提供第二个参数。 如果 get 会导致模板推导失败的话,那这个例子里就可以使用类似于课中 void_t 的方法了。不过,对不存在的成员进行 get 虽然会产生编译失败,这个失败不是推导失败,而是静态断言。因而 void_t 也解决不了问题。 至于你说的逗号表达式,我看不出解决了什么问题。我试验下来也没看到非 1 的结果。
共 2 条评论 - 禾桃2020-02-16https://en.cppreference.com/w/cpp/types/enable_if " Possible implementation template<bool B, class T = void> struct enable_if {}; template<class T> struct enable_if<true, T> { typedef T type; }; " 下列的测试代码可以编译,运行 " #include <type_traits> #include <iostream> template <typename X, typename T, typename Y> typename std::enable_if<std::is_same<T, int>::value>::type update(X x, T t, Y y) { std::cout << "int" << std::endl; } int main () { float a = 1.4; int b = 1; double c =2.2; update(a, b, c); } " 其中我用的是std::enable_if<std::is_same<T, int>::value>, 并没有提供参数来推导T, 这是不是意味着对于true这种情况的偏特化implementation是 template<class T = void> struct enable_if<true, T> { typedef T type; }; 而不是 template<class T> struct enable_if<true, T> { typedef T type; }; 谢谢!展开
作者回复: 我觉得应该这么理解: 你给出的表达式是 typename std::enable_if<std::is_same<T, int>::value>::type 估值后得到 typename std::enable_if<true>::type 实例化时,编译器看到没有提供第二个参数,加上缺省参数按 std::enable_if<true, void> 实例化,再偏特化得到 template<> struct enable_if<true, void> { typedef void type; };
共 2 条评论