C++ SFINAE

1. 什么是SFINAE


在C++中有很多的编程技巧(Trick), SFINAE就是其中一种,
他的全义可以翻译为”匹配失败并不是一个错误(Substitution failure is not an error)“.
简单来说他就是专门利用编译器匹配失败的一种技巧.

2. 案例

比如我们想实现一个通用的函数叫AnyToString, 他可以实现任意类型的数据转成字符串:

1 template<typename ValueType>
2 char* AnyToString(const ValueType& value);

我们更希望这个函数能检查ValueType类型自己有没有ToString方法, 如果有就直接调用,
没有的话就采取通用的处理方案. 但是C++没有反射机制,
不能像C#那样通过TypeInfo来检查,
更没有像Java那样纯粹的OOP,从最基类就定义了ToString方法,下面的子类只用负责重载。

所以我们希望能有一种方法能让C++也能检查某个类型是否定义了某个成员函数,
这就可以用到SFINAE.

3. 解决方案

C++的模板匹配有个特点, 编译器始终会寻找类型匹配最精确的模板. 当然并不一定所有的模板都能匹配,
一旦有某个模板匹配不成功, 编译器会自动尝试别的候选模板, 要是所有的都不成功那编译器就匹配失败, 有的时候我们想故意跳过某些精确度高模板匹配,
而使用精确度低的模板, 这个时候就可以利用SFINAE故意让编译器匹配失败. 回到案例,
我们希望检查一个类型是否有ToString方法, 例如:

class A { char* ToString(); };

class B { };

这时我们在代码里面写A::ToString, 自然没有什么问题,
但是如果写B::ToString的话编译将告诉你找不到这个符号. 我们可以利用这个错误来跳过某些模板的匹配,
而使得别的模板可以得到匹配. 例如以下代码:


 1 template<typename ClassType>
2 struct HasToStringFunction {
3 typedef struct { char[2]; } Yes;
4 typedef struct { char[1]; } No;
5
6 template<typename FooType, char* (FooType::*)()>
7 struct FuncMatcher;
8
9 template<typename FooType>
10 static Yes Tester(FuncMatcher<FooType, &FooType::ToString>*);
11
12 template<typename FooType>
13 static No Tester(...);
14
15 enum {
16 Result = sizeof(Tester<ClassType>(NULL)) == sizeof(Yes)
17 };
18 };
19
20 bool a_has_tostring = HasToStringFunction<A>::Result; // True
21 bool b_has_tostring = HasToStringFunction<B>::Result; // False

这里有两个Tester方法,
第一个的匹配精度高于第二个的.

当编译器解析Tester<ClassType>(NULL)的时候,
编译器首先会尝试用ClassType以及他的一个ClassType::ToString方法去实例化一个FuncMatcher类型来匹配第一个Tester函数.
对于A来说, 这是能通过的.

但是对于B来说, 因为其没有ToString方法,
所以不能用B以及不存在的B::ToString来实例化FuncMatcher.

这个时候编译器实际上就已经发现错误了, 但是根据SFINAE原则这个只能算是模板匹配失败, 不能算错误,
所以编译器会跳过这次对FuncMatcher的匹配. 但是跳过了以后也就没有别的匹配了,
所以整个第一个Tester来说对B都是不能匹配成功的,
这个时候优先级比较低的第二个Tester自然就能匹配上了.
我们就可以利用这一点来实现我们最开始的想要AnyToString方法:


template<bool>
struct AnyToStringAdviser;

template<>
struct AnyToStringAdviser<true> {
template<typename ValueType>
static char* ToString(const ValueType& value) {
return value.ToString();
}
}

template<>
struct AnyToStringAdviser<false> {
template<typename ValueType>
static char* ToString(const ValueType& value) {
/* Generic process */
}
}

template<typename ValueType>
char* AnyToString(const ValueType& value) {
return AnyToStringAdviser<HasToStringFunction<ValueType>::Result >::ToString(value);
}

4. 再写一个常用的使用了该方法的traits工具类


 1 template <typename T>
2 struct is_class{
3 typedef char __one__;
4 typedef struct{ char[2]; } __two__;
5
6 template <typename U>
7 static __one__ test(int U::*){ }
8
9 template <typename U>
10 static __two__ test(...){ }
11
12 const static bool value = (sizeof(test<T>(NULL)) == sizeof(__one__));
13 };

C++ SFINAE,布布扣,bubuko.com

时间: 2024-10-11 05:04:20

C++ SFINAE的相关文章

【转】C++中的SFINAE

C++中的SFINAE 2011-05-11 15:45:20 分类: C/C++ 这几天神游到一段is_base_of的代码迷惑了很久, 在查资料的过程当中, 发现C++中一种称之为SFINAE的技巧, 全称为"匹配失败并不是一种错误(Substitution Failure Is Not An Error)". 这是一种专门利用编译器匹配失败来达到某种目的的技巧. 在说明之前先说说模板匹配的原则: 非模板函数具有最高优先权, 如果不存在匹配的非模板函数的话, 那么最匹配的和最特化的

C++14 SFINAE 解引用迭代器

C++14 SFINAE 解引用迭代器 p { margin-bottom: 0.25cm; line-height: 120% } a:link { } 原问题:编写函数f(r),若r为迭代器,则返回f(*r),否则返回r. p { margin-bottom: 0.25cm; line-height: 120% } a:link { } 摘要: p { margin-bottom: 0.25cm; line-height: 120% } a:link { } 问题: 什么是迭代器? 迭代器是

C++模板元编程 - 3 逻辑结构,递归,一点列表的零碎,一点SFINAE

本来想把scanr,foldr什么的都写了的,一想太麻烦了,就算了,模板元编程差不多也该结束了,离开学还有10天,之前几天部门还要纳新什么的,写不了几天代码了,所以赶紧把这个结束掉,明天继续抄轮子叔的Win32库去. 逻辑结构和递归说白了就是做了一个If,一个For_N,If就和Excel里的If一样,For_N是把一个模板结构迭代N遍,为了所谓的方便,把If做成了宏,写起来还挺有意思的 1 template<typename TTest, typename TTrue, typename TF

SFINAE and enable_if

There's an interesting issue one has to consider when mixing function overloading with templates in C++. The problem with templates is that they are usually overly inclusive, and when mixed with overloading, the result may be surprising: 因为模板的包容性太强,因

C++模板进阶指南:SFINAE

C++模板进阶指南:SFINAE 空明流转(https://zhuanlan.zhihu.com/p/21314708) SFINAE可以说是C++模板进阶的门槛之一,如果选择一个论题来测试对C++模板机制的熟悉程度,那么在我这里,首选就应当是SFINAE机制. 我们不用纠结这个词的发音,它来自于 Substitution failure is not an error 的首字母缩写.这一句之乎者也般难懂的话,由之乎者 -- 啊,不,Substitution,Failure和Error三个词构成

More C++ Idioms

Table of Contents Note: synonyms for each idiom are listed in parentheses. Adapter Template TODO Address Of                            Readed,没啥用 Algebraic Hierarchy    Readed,没啥用 Attach by Initialization Readed,没啥用 Attorney-Clie nt 有点用 Barton-Nackma

跨平台渲染框架尝试 - Texture管理

纹理是渲染器重要的资源,也是比较简单的资源.本文将详细讨论纹理资源的管理. 在资源管理概述中提到,资源就是一堆内存和CPU与GPU的访问权限.纹理管理在资源管理之上,要负责如何使用者一堆内存构造纹理对象,并告诉渲染器如何使用平台相关的纹理对象.下面,我们开始详细论述. 1. 纹理资源 首先纹理资源是GPU可以使用到的资源.它与Buffer资源不同的地方在于,相邻像素的插值计算中,纹理比Buffer简单并快得多,因为有相应的硬件实现.纹理资源字面意义上就像是一张像素图,但它不仅限于二维的像素的图,

json 对c++类的序列化(自动生成代码)

[动机] 之前写网络协议的时候,使用的是google protobuf,protobuf不但在性能和扩展性上有很好的优势,protoc自动生成c++类代码的工具,这点确实给程序员带来了很多便利. 做后面一项目使用的json格式来传输,然后就萌生了实现像protoc这样的工具,根据json文件来生成c++类代码,并且生成序列化代码,这样在写网络的时候就无需把jsonvalue序列化散落在各处. [思路] 之前写object-c的时候,如果你要对类的序列化,你必须实现NSCoding协议(接口),

(原创)舌尖上的c++--相逢

引子 前些时候,我在群里出了一道题目:将变参的类型连接在一起作为字符串并返回出来,要求只用函数实现,不能借助于结构体实现.用结构体来实现比较简单: template<typename... Args> struct Connect; template< typename First, typename... Rest> struct Connect<First, Rest...> { static string GetName() { return typeid(Fir