By Z.H. Fu
https://fuzihaofzh.github.io/blog/
C++的enable_if常用于构建需要根据不同的类型的条件实例化不同模板的时候。本文主要讲了enable_if的使用场景和使用方式。
## 函数重载的缺陷
函数重载能解决同名函数针对不同传入参数类型而实现不同的功能。举一个简单的例子:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | void print(int a){cout<<"in int print";
 }
 void print(double a){
 cout<<"in double print";
 }
 int main(){
 f(1);
 f(1.0);
 }
 
 | 
输出:
in int f()
in double f()
可以看出,这里的选择方式是通过不同的参数类型实现的。那么问题来了,如果我们是写的模板,想根据模板的条件来选择实现该怎么办?(例如,对于我们定义的一些class做输入时,采用一种方式实现,而对于其他类型的话采用另一种方式)。这就需要用到enable_if
 SFINAE原则与enable_if简介
C++模板函数重载依赖于 SFINAE (substitution-failure-is-not-an-error) 原则,即替换失败不认为是错误,而只是简单地pass掉。看下面一个例子:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | #include <iostream>using namespace std;
 void f(double a){
 cout<<"in double f()"<<endl;
 }
 template<typename T>
 void f(typename T::noexist a){
 cout<<"in T::noexist f()"<<endl;
 }
 int main(){
 f(1);
 f(1.0);
 }
 
 | 
程序正常编译通过,输出:
in double f()
in double f()
可以看到double和int都没有一个叫noexist的类型,所以解析是失败的,但是直接跳过,调用f的时候都转换为double输出。利用这个原则,我们可以构建一个开关的类,当满足某一条件时,让某类型能出现,不满足时,让他没有该类型,解析失败。这个开关函数就是 enable_if。enable_if是c++的标准模板,其实现非常简单,这里我们给出其实现的一种方式:
| 12
 3
 4
 
 | template<bool B, class T = void>struct user_enable_if {};
 template<class T>
 struct user_enable_if<true, T> { typedef T type; };
 
 | 
这里我们部分偏特化了当条件B为true时的模板user_enable_if,与普通的user_enable_if的区别就在于定义了type类型,这样,用户使用typename user_enable_if<cond, Type>::type时,当cond为true时,这个表达式是一个类型,而当cond为false时,该表达式解析失败。
 enable_if的使用场景
enable_if可以作为参数或返回值加到函数中,我们看具体的例子:
- 作为参数传入
 我们在函数参数里多加了一个参数作推导用。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
 | #include <iostream>using namespace std;
 template<bool B, class T = void>
 struct user_enable_if {};
 template<class T>
 struct user_enable_if<true, T> { typedef T type; };
 
 struct A{};
 
 template<typename T>
 struct Traits{
 static const bool is_basic = true;
 };
 
 template<>
 struct Traits<A>{
 static const bool is_basic = false;
 };
 
 template<typename T>
 void f(T a, typename user_enable_if<Traits<T>::is_basic, void>::type* dump= 0){
 cout<<"a basic type"<<endl;
 }
 
 template<typename T>
 void f(T a, typename user_enable_if<!Traits<T>::is_basic, void>::type* dump= 0){
 cout<<"a class type"<<endl;
 }
 
 int main(){
 A a;
 f(1);
 f(a);
 }
 
 | 
运行输出:
a basic type
a class type
在这里,当f的输入是1时,Traits::is_basic为true,user_enable_if<Traits::is_basic>::type能得到一个type(void),因此能实例化,而第二个模板不能实例化。而当f的输入是a时,结果正好相反。但有时后我们对参数个数有限制(例如,我们是重载的operator函数,参数个数被严格限制),这时候我们可以把enable_if加到返回值上。
- 作为返回值
 代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | #include <iostream>using namespace std;
 template<bool B, class T = void>
 struct user_enable_if {};
 template<class T>
 struct user_enable_if<true, T> { typedef T type; };
 
 struct A{};
 
 template<typename T>
 struct Traits{
 static const bool is_basic = true;
 };
 
 template<>
 struct Traits<A>{
 static const bool is_basic = false;
 };
 
 template<typename T>
 typename user_enable_if<Traits<T>::is_basic, T>::type f(T a){
 cout<<"a basic type"<<endl;
 return a;
 }
 
 template<typename T>
 typename user_enable_if<!Traits<T>::is_basic, T>::type f(T a){
 cout<<"a class type"<<endl;
 return a;
 }
 
 int main(){
 A a;
 f(1);
 f(a);
 }
 
 | 
在这里,我们把enable_if用到了返回值上,当传入是1时,user_enable_if<Traits::is_basic, T>::type 为T(即int),而user_enable_if<!Traits::is_basic, T>::type无法解析,因此使用第一个模板实例化。当传入为a时相反。