std::enable_if的理解
引出问题
查看同事写的代码时,有一段代码自己理解了很长时间,在这里记录一下。下面的代码片段是我简化后的部分
#include <iostream>
using namespace std;
class Father {
public:
void print(ostream &os) { os << "Father" << endl; }
enum { E_PRINT_ENABLE = 1, };
};
class Son : public Father {
public:
void print(ostream &os) { os << "Son" << endl; }
enum { E_PRINT_ENABLE = 0, };
};
template<class T, class = enable_if<T::E_PRINT_ENABLE>::type>
ostream & operator << (ostream &os, T & t) {
t.print(os);
return os;
}
int main()
{
Father cFather;
cout << cFather << endl;
// compile error C2679
Son cSon;
cout << cSon << endl;
}
当时主要是以下几点不太明白,后面的部分会着重介绍
- enable_if 的使用
- typename 的作用
- 模板函数operator << 中第二个模板类型参数使用 class = 的含义
enable_if 使用
enable_if 的介绍参考 http://en.cppreference.com/w/cpp/types/enable_if。具体的源码实现也很简单,通过模板和模板的具体化来实现的,简单的来说就是当模板参数B为true时定义一个类型type(它的类型为T)
// #1
template<bool B, class T = void>
struct enable_if {};
// #2
template<class T>
struct enable_if<true, T> { typedef T type; };
关于模板的实例化和具体化相关的信息参考另外一篇文章 C++模板的具体化和实例化
operator << 的实现原理
operator << 从实现上来看是重载了 << 运算符,使得下面的语句符合语法。
Father cFather;
cout << cFather << endl;
与普通的重载 << 运算符相比,这里唯一的不同是使用了模板,模板的第一个类型参数还好理解,第二个类型参数 class = enable_if::type 其实等价于 class L = enable_if::type,因为在函数的实现过程中没有使用到这个类型参数,所以直接不写类型变量的名称。下面就引出了两个问题
第二个类型参数到底是什么类型
- 如果 T::E_PRINT_ENABLE 为 false,根据模板(#1) 的实现,它是没类型的
- 如果 T::E_PRINT_ENABLE 为 ture 时按照模板的具体化(#2)来实现的,class = enable_if::type 的类型应该是模板具体化(#2)中的第二个类型参数,如果没有确定第二个类型参数的话,会使用模板定义(#1)中的默认参数,所以当 T::E_PRINT_ENABLE 为 true 时,返回的类型是 void
为什么要加上第二个类型参数
在函数的实现过程中根本不需要第二个类型参数,这里添加第二个类型参数主要是想限制 operator << 只对 Father 类对象有效,对 Son 类对象无效。
Father 类对象中 T::E_PRINT_ENABLE 为 1,oparot << 实例化的时候,第一个类型参数的类型为 Father,第二个类型参数中 enable_if::type 为void,既第二个类型参数默认为void。所以#1部分的代码可以正常编译通过
Son 类对象中 T::E_PRINT_ENABLE 为 0,oparot << 实例化的时候,第一个类型参数的类型为 Son,第二个类型参数中 enable_if::type 没有值,还需要传入第二个类型参数的类型后才能正常实例化。所以#2部分的代码不能正常编译通过
后记
其实完全可以使用一个成员变量来控制 operator << ,这样程序在编译的时候不会报错,程序运行的过程中才去判断是否打印。原作者这样写,可能是考虑到将是否打印的判断放到了编译阶段,提高运行的效率