c++11中有一个特殊的模板类integral_constant,首先看实现:
template
struct integral_constant
{
static constexpr _Tp value = __v;
typedef _Tp value_type;
typedef integral_constant<_Tp, __v> type;
constexpr operator value_type() const { return value; }
#if __cplusplus > 201103L
#define __cpp_lib_integral_constant_callable 201304
constexpr value_type operator()() const { return value; }
#endif
};
代码很简单,它只做了几件事:定义了一个_TP类型的value成员变量并赋值为__v,用typedef声明了value_type和type,重载了value_type和()操作符。使用起来很简单:
typedef std::integral_constant two_t;
typedef std::integral_constant four_t;
typedef std::integral_constant five_t;
static_assert(two_t::value*2 == four_t::value, "always true");
static_assert(two_t::value*2 == five_t::value, "always false");
看起来烤箱没有什么特殊的地方,实际上对于模板元编程来说,它的用处可大了,在STL里很多地方用到,type_traits里面定义的非常重要的两个integral_constant类型:
/// The type used as a compile-time boolean with true value.
typedef integral_constant true_type;
/// The type used as a compile-time boolean with false value.
typedef integral_constant false_type;
class IamTrue : public true_type {
};
cout << IamTrue::value << endl; // 1
接下来看c++如何在编译时做类型判断:
template struct is_same : public false_type { };
template struct is_same<_Tp, _Tp>: public true_type {
class TestA{};
typedef TestA ToTestA;
class TestB{};
int main() {
cout << is_same::value << endl; // 1
cout << is_same::value << endl; // 0
return 0;
}
这里说明一下为什么会有这样的结果,首先第一个编译时由于c++的编译时选择性,发现第一个模板类符合要求所以返回true,第二个编译时由于参数不是同一个类型所以选择了false_type。type_traits头文件中还有很多类似的功能复杂的模板类,都是利用了编译时的选择性,由于是编译决定的,所以没有任何的运行时消耗。
下面我们看如何判断一个类是一个纯虚类。参考:点击打开链接
struct __two{char __lx[2];};
namespace _is_abstract
{
template char __test(_Tp (*p)[1]);
template __two __test(...);
}
__is_abstract_imp命名空间里声明了两个重载的模板函数,返回值分别为char和__two。声明这两个函数的意义在于,我们可以通过编译期重载函数的匹配来判断传入的模板参数是否符合我们的要求,判断的方式也很简单,只要使用sizeof操作符就行了:
#define __is_abstract_(_T) sizeof(_is_abstract::__test<_T>(0)) == 2
template
struct is_abstract_imp : public integral_constant { };
上行代码的含义也很简单明了,即匹配第一个重载函数时,sizeof的结果为1,匹配第二个重载函数时sizeof的结果为2,所以我们就实现了编译期的类型判断。
原理就是利用了抽象类无法实力化的特性,首先参数p就是一个指向_TP类型的数组的指针(即数组指针),后面的[1]代表的是指向一个只拥有一个元素的数组的指针,这里至少为1,多了也没有意义,如果你将其改成0,会造成编辑器的警告,因为定义了一个指向0个元素的数组的指针。那么接下来就很好理解了,因为纯虚类无法实例化,所以我们无法定义一个纯虚类对象的数组(注意不是纯虚类对象指针的数组,即是TP a[1],不是TP* a[1]),所以在遇到纯虚类时第一个重载函数匹配会失败,而遇到非纯虚类第一个重载函数却能匹配成功,所以我们现在就能够通过这个模板来判断传入模板参数的类型是否为纯虚类类型了。
class A {
};
class B {
public:
virtual void Say() = 0;
};
int main()
{
std::cout << is_abstract_imp::value << '\n';
std::cout << is_abstract_imp::value << '\n';
return 0;
}
到这里我们就算完成了,当然这个方法标准库里也有,有一个关键的地方看不到其具体实现,这里给实现以下。有的人可能还会有疑问,为什么我们需要这样写呢,写成下面这样不行么,不是一样也能判断模板的参数类型么,而且还更好理解:
namespace __is_abstract_imp
{
template char __test(_Tp);
template __two __test(...);
}
sizeof在计算函数返回值大小的时候必须给函数传入一个参数,并且我们需要让sizeof的计算发生在编译期而非运行期,我们是不可能创建一个_TP对象传进去的,也就是说这里__test最理想的参数就是指针了,我们只需要传入0作为参数就可以了,所以这里最好的参数选择就是指针数组了。