最近在项目中用到了类型萃取,写一文章记录一下
类型萃取解决了什么问题?
- 如何在编译期间判断某个类型 T 是否是 class 类
- 判断某个 class T 是否有指定的成员变量或成员函数?
要了解类型萃取,首先要学一下模版特化
模板特化
有时为了需要,针对特定的类型,需要对模板进行特化,也就是所谓的特殊处理。比如有以下代码:
template<typename C>
int test(C c) {
cout<<"for all type"<<endl;
return c.size();
}
我们写了一个模板函数test,如果我们在main函数中执行如下语句
int main() {
string s = "sss";
test<string>(s);//正确,输出“for all type”
test<int>(1); //报错,int型没有size()
return 0;
}
显然test<int>(1); 会报错,因为int型并没有size()方法,此时我们再写一个也叫test的函数:
//为int提供的特化版本test函数
template<typename C>
int test(int i) {
cout<<"for int type"<<endl;
return 0;
}
我们将此称为test函数的int型特化版本,我们希望如果test()方法接收到int类型的参数会调用该特化版本,相当于给int型开了个小灶,如果我们再次执行之前的main 函数
int main() {
string s = "sss";
test<string>(s);//正确,输出“for all type”
test<int>(1); //不会报错了,输出"for int type"
return 0;
}
此时,test<int>(1)不会报错了,而是输出"for int type"。
我们把这叫做模板特化,告诉编译器对于某种类型进行特殊输处理(开小灶),上述例子中我们对int型进行了特殊处理,此时如果我们在main函数中执行
test<double>(1); //报错,没对double型特殊处理,double型没有size()方法
还是会报错,因为我们并没有double型的特化
下面将介绍模板特化具体能解决什么问题
判断类型 T 是否是一个class
我们想在编译期间判断一个类型T是否是一个类,套用 SFINAE 原则,我们需要两个返回不同值的模板函数
- 当传入 Class 作为模板实参时匹配到其中一个函数模板
- 而传入其他实参时匹配到另一个。这样我们就可以根据返回值将 class 与其他类型区分开。
先上主要部分代码
template<typename T>
class IsClass {
private:
typedef char One;
typedef struct { char a[2]; } Two;
template<typename C> static One test(int C::*);
template<typename C> static Two test(...);
public:
static bool value ;//= sizeof(IsClass<T>::test<T>(NULL)) == 1;
};
template<typename T>
bool IsClass<T>::value = sizeof(IsClass<T>::test<T>(NULL)) == 1;
这里解释一下,
- int C::*表示指向class C的成员的指针
- sizeof ()操作符可以在编译期间返回指定的类型占用的空间大小
我们再随便定义一个类Test
class Test {
};
在main函数里执行下列语句语句
int main() {
bool is_class = IsClass<Test>::value;
std::cout << is_class << value; //Test是一个class,输出1
bool is_class2 = IsClass<int>::value;
std::cout << is_class2 << value; //int不是一个类,输出0
}
解释一下原因,对于语句
bool is_class = IsClass<T>::value;
- 如果T不是一个类,比如T是一个int型,在计算value时会调用返回Two的test函。因为返回One的参数是 int C::*,int型不具有指向其函数的指针,编译器匹配不上。
- 如果T是一个类,能够匹配上返回One的test函数,NULL能够被推导成int T::*。
- 有同学会问,T是一个类,也能匹配上返回One的test方法,怎么在二者之间做选择呢?《C++ templates》书中说道:
overload resolution prefers the conversion from zero to a null pointer constant over binding an argument to an ellipsis parameter (ellipsis parameters are the weakest kind of binding from an overload resolution perspective).
意思就是:省略号参数是最弱的绑定类型,所以最后只有当其他都匹配不上才会去匹配参数为省略号的函数
判断class T 是否有某个成员函数
假设有Man和Woman两个类:
class Man {
public:
void man_do(){}
static void sayhi() {
cout << "im a man" << endl;
}
};
class Woman {
public:
void woman_do(){}
static void sayhi() {
cout << "im a woman" << endl;
}
};
我们有如下main函数:
int main()
{
Say<Man>();
Say<Woman>();
return 0;
}
我们希望Say<T>()函数能够通过判断T类型有没有一个叫做"man_do()"的方法,如果有,就执行man::sayhi(),没有就执行woman::sayhi()。
来看具体实现,先定义Say函数,Say()函数会调用Check结构体的type类的sayhi()方法,Say的定义如下:
template<class P>
void Say() {
Check<P>::type::sayhi();
}
再来看Check结构体和IF结构体的内容:
template <class P>
struct Check {
typedef typename IF<Trait<P>::has_mando, Man, Woman>::type type;
};
template <bool C, class T, class E>
struct IF { typedef T type; }; //非特化IF
template <class T, class E>
struct IF<false, T, E> { typedef E type; }; //标明了false,特化版本的IF
可以看出,IF的type决定了typename的type,如果IF<>模板第一个参数是false,Check的type就是Woman,如果IF的模板第一个参数是true,那么Check的type就是Man。关键就在于传给IF第一个参数的Trait<P>::has_mando,来看定义:
template <class P, class... Args>
struct Trait {
static const bool has_mando = HasMando<P>::value;
};
Trait有一个成员变量has_mando,由HasMando<P>::value决定,再来看HasMando结构体
template <class T>
struct HasMando {
typedef char yes[1];
typedef char no[2];
template <class> static no& test(...);
template <class U> static yes& test(decltype(&U::man_do));
static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};
这是不是和上一节的方法很像?是通过sizeof()+匹配不同函数实现判断。
现在从底往上推导
如果类T有man_do方法,会匹配上返回yes的test()方法,因此HasMando<T>结构体的value就等于true。
再来看Trait的定义
template <class P, class... Args>
struct Trait {
static const bool has_mando = HasMando<P>::value;
};
Trait的has_mando等于HasMando<P>的value,因此也等于true
再往上到了Check和IF:
template <class P>
struct Check {
typedef typename IF<Trait<P>::has_mando, Man, Woman>::type type;
};
template <bool C, class T, class E>
struct IF { typedef T type; }; //非特化IF
template <class T, class E>
struct IF<false, T, E> { typedef E type; }; //标明了false,特化版本的IF
IF有特化和非特化两个版本,Trait<P>::has_mando是true,匹配上非特化版本的IF模板,因此IF的type为Man,Check的type也为man
最后,Say函数调用Man的sayhi()函数
template<class P>
void Say() {
Check<P>::type::sayhi();
}
check<P>的type是Man,调用Man的sayhi(),结束
最后把所有代码放在这里
#include <iostream>
using namespace std;
class Man {
public:
void man_do(){}
static void sayhi() {
cout << "im a man" << endl;
}
};
class Woman {
public:
void woman_do(){}
static void sayhi() {
cout << "im a woman" << endl;
}
};
template <class T>
struct HasMando {
typedef char yes[1];
typedef char no[2];
template <class> static no& test(...);
template <class U> static yes& test(decltype(&U::man_do));
static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};
template <class P, class... Args>
struct Trait {
static const bool has_mando = HasMando<P>::value;
};
template <bool C, class T, class E>
struct IF { typedef T type; };
template <class T, class E>
struct IF<false, T, E> { typedef E type; };
template <class P>
struct Check {
typedef typename IF<Trait<P>::has_mando, Man, Woman>::type type;
};
template<class P>
void Say() {
Check<P>::type::sayhi();
}
int main()
{
Say<Man>();
Say<Woman>();
return 0;
}