C++中的SFINAE
来源:互联网 发布:小非农的数据什么时间 编辑:程序博客网 时间:2024/06/05 18:25
来源:http://blog.chinaunix.net/uid-1720597-id-306773.html
这几天神游到一段is_base_of的代码迷惑了很久, 在查资料的过程当中, 发现C++中一种称之为SFINAE的技巧, 全称为"匹配失败并不是一种错误(Substitution Failure Is Not An Error)". 这是一种专门利用编译器匹配失败来达到某种目的的技巧.
在说明之前先说说模板匹配的原则: 非模板函数具有最高优先权, 如果不存在匹配的非模板函数的话, 那么最匹配的和最特化的具有最高的优先权.
C++中,函数模板与同名的非模板函数重载时,应遵循下列调用原则:
- 寻找一个参数完全匹配的函数,若找到就调用它。若参数完全匹配的函数多于一个,则这个调用是一个错误的调用。
- 寻找一个函数模板,若找到就将其实例化生成一个匹配的模板函数并调用它。
- 若上面两条都失败,则使用函数重载的方法,通过类型转换产生参数匹配,若找到就调用它。
- 若上面三条都失败,还没有找都匹配的函数,则这个调用是一个错误的调用。
至于函数的选择原则, 可以看看C++ Primer中的说明:
- 创建候选函数列表,其中包含与被调用函数名字相同的函数和模板函数。
- 使用候选函数列表创建可行的函数列表。这些都是参数数目正确的函数,并且有一个隐式的转换序列(参数类型转化),其中包括实参类型与相应的形参类型完全匹配情况。
- 确定是否有最佳的可行函数,有则调用它,没有则报错。
- 完全匹配,但常规函数优先于显示定义模板函数,而显示定义模板函数优先于模板函数。
- 提升转换,即从小精度数据转换为高精度数据类型,如char/short 转换为int , int转化为long,float转换为double。
- 标准转换,如int转化为char,long转化为double等
- 用户自定义转换。
下面先看看带有默认值的模板函数特化和非特化的问题
- template<typename T, bool C = true>
- struct if_ {
- static const int value = 1;
- };
- template<typename T>
- struct if_<T, true> {
- static const int value = 2;
- };
- int main() {
- printf("value: %d\n", if_<int>::value);
- }
上面的输入结果是: value: 2. 编译器在进行匹配的时候, 就如Prime上说的, 编译器会先创建候选函数列表, 在创建候选列表的过程中C已经被赋予默认是true, 然后在进行匹配, 最高优先级是函数, 显然这里没有. 然后是最匹配和最特化的模板函数, 所以, 就匹配到第二个函数了, 因此value等于2.
实际上, 上面的例子相当于
- template<typename T, bool C = true>
- struct if_ {};
- template<typename T>
- struct if_<T, false> {
- static const int value = 1;
- };
- template<typename T>
- struct if_<T, true> {
- static const int value = 2;
- };
- int main() {
- printf("value: %d\n", if_<int>::value);
- }
因此, 编译器总是先找到函数和模板, 然后根据默认值, 类型转换进行匹配, 然后再根据优先级选择最可行的.
-----------------------------------------华丽的分割线--------------------------------------------------
说到这里应该大致明白重载函数, 函数模板, 特化和偏特化的一些匹配问题. (其实还有一个问题没有说清楚, 后面会提到). 现在来看看SFINAE的一个应用.
要实现一个通用的序列化函数叫做toString, 它可以实现把任何类型序列化成字符串.
- template<typename T> std::string toString(const T &x);
现在有两个类
- class A {
- public:
- std::string toString() const {
- return std::string("toString from class A");
- }
- };
- class B {
- };
这时代码里面有A::toString 就没有问题, 但是编译器找不到B::toString, 利用这个错误来跳过模板的匹配, 从而使得别的模板得以匹配.
- template<typename T>
- struct HasToStringFunction {
- template<typename U, std::string (U::*)() const >
- struct matcher;
- template<typename U>
- static char helper(matcher<U, &U::toString> *);
- template<typename U>
- static int helper(...);
- enum { value = sizeof(helper<T>(NULL)) == 1 };
- };
利用这点就可以实现toString方法
- template <bool>
- struct ToStringWrapper {};
- template<>
- struct ToStringWrapper<true> {
- template<typename T>
- static std::string toString(T &x) {
- return x.toString();
- }
- };
- template<>
- struct ToStringWrapper<false> {
- template<typename T>
- static std::string toString(T &x) {
- return std::string(typeid(x).name());
- }
- };
- template<typename T>
- std::string toString(const T &x) {
- return ToStringWrapper<HasToStringFunction<T>::value>::toString(x);
- }
- int main() {
- A a;
- B b;
- std::cout << toString(a) << std::endl;
- std::cout << toString(b) << std::endl;
- }
这里有两个小技巧, 一个是sizeof()一个函数, 返回的是函数返回值的大小. 另外一个是U::*表示类成员函数指针. 比如std::string (*)() const 表明这是一个函数指针, 而std::string (U::*)() const表示这是一个类的成员函数.
这样我们可以实现一个判断类型是基本类型还是一个类的类模板is_class
- template <typename T>
- class is_class {
- template <typename U>
- static char helper(int U::*);
- template <typename U>
- static int helper(...);
- public:
- static const bool value = sizeof(helper<T>(0)) == 1;
- };
------------------------------------------------华丽的分割线--------------------------------------------
现在终于轮到大名鼎鼎的is_base_of出场
- template <typename T1, typename T2>
- struct is_same {
- static const bool value = false;
- };
- template <typename T>
- struct is_same<T, T> {
- static const bool value = true;
- };
- template<typename Base, typename Derived, bool = (is_class<Base>::value && is_class<Derived>::value)>
- class is_base_of {
- template <typename T>
- static char helper(Derived, T);
- static int helper(Base, int);
- struct Conv {
- operator Derived();
- operator Base() const;
- };
- public:
- static const bool value = sizeof(helper(Conv(), 0)) == 1;
- };
- template <typename Base, typename Derived>
- class is_base_of<Base, Derived, false> {
- public:
- static const bool value = is_same<Base, Derived>::value;
- };
- template <typename Base>
- class is_base_of<Base, Base, true> {
- public:
- static const bool value = true;
- };
先来看看is_base_of模板的第三个参数, 如果前两个类型都是类的话, 则为true, 否则为false. 下面一个个情况来分析:
第一种情况是有一个基本类型, 显然, is_base_of第三个参数为false, 按照上面说到的原则匹配到了第二个类模板. 在该情况下只有当两个类型一致时is_base_of的value才为true, 否则为false.
第二种情况是Base和Derived是同一个类型, 则会匹配到第三个模板.
第三种情况是Base和Derived有继承关系, 此时编译器只能匹配第一个类模板. 在helper(Conv(), 0)中, 显然没有helper的第一个参数无法直接匹配, Conv()默认也无法转换成Base或者是Derived. 因此需要调用自定义的转换函数. 当试图匹配int helper(Base, int)的时候, 编译有两个途径, 一个是: Conv()->Derived()->Base(), 第三步是默认, 另一个是Conv()->const Conv()->Base(), 这实际上是一个SFINAE, 根据原则编译器会继续匹配下一个模板, 匹配成功, 因此value的值为true.
第四种情况是Base和Derived没有继承关系, 当试图匹配int helper(Base, int)的时候能通过Conv()->const Conv()->Base()匹配成功, 因为两者不是继承关系, 因此Derived()->Base()的默认转换不会成功, 因此该情况下此路径不存在. 所以编译器选择的函数是int helper(Base, int), 最后, value的值为false.
----------------------------------- 华丽的分割线 ------------------------------------------------------
结束语: 无语.
0 0
- C++中的SFINAE
- C++中的SFINAE
- SFINAE
- SFINAE
- 由is_base_of看C++中的SFINAE
- SFINAE使用
- result_of SFINAE
- 深入浅出SFINAE
- 深入浅出SFINAE
- SFINAE应用一
- SFINAE应用二
- SFINAE应用三
- SFINAE 极简介绍
- c++模板之SFINAE
- boost::enable_if与SFINAE原则
- C++惯用法之SFINAE
- C++模板之SFINAE技术
- C++ 重载决议overload resolution 与 SFINAE
- 【转】专访雷果国:从1.5K到18K 一个程序员的5年成长之路
- 第3周项目3——多文件组织
- 2的次幂表示
- MAVEN常用命令
- iOS-delegate设计模式
- C++中的SFINAE
- linux知识
- Android网络图片下载框架—Volley
- 【树链剖分】重建
- Majority Element
- java字符串替换的问题
- android studio 导入 .so文件
- IOS:IOS集成开发和环境的介绍
- Sublime使用技巧--Zencoding快速生成html