C++超详细分析type_traits
目录
- 定义基础常量
- 基础类型判断
- 类型处理
- 类型选择
- 判断是否相同
- tips
- 实现is_base_of
本篇文章旨在引导大家自行实现type_traits的基础代码。
模板编程不像常规的代码,可以有if-else这些流控制语句,我们需要充分利用模板、模板特例、类型转换等特性来实现编译期的一系列判断和类型转换。
定义基础常量
第一步,我们需要定义true和false两个常量,所有的type_traits都基于此。我们的目的就是要用一个模板类型来表示是非,其中的value正好是这两个值。之后我们更高级的判断类型都是继承自这两个类型的其中一个,通过这种方式获取value值就可以获取true和false了。
如果听这个解释有点晕的话,不要紧,我们直接来看代码。这里需要注意的是,既然type_traits都是编译期行为,因此其成员只能是静态不可变成员(编译期就可以确定的成员)。
struct true_type { static constexpr bool value = true; }; struct false_type { static constexpr bool value = false; };
基础类型判断
有了基础常量,我们可以先做一些简单的类型判断,比如说判断这个类型是不是void。这里的思路是,针对于所有类型的模板,继承自false_type,而针对于void类型,我们给予一个模板特例,让他继承自true_type。这样一来,只有当类型是void的时候才会推导true,其他的就会推导false。请看例程:
template <typename> struct is_void : false_type {}; template <> struct is_void<void> : true_type {};
这里我们可以做一些简单的测试,来判断函数的返回值是否为void:
void test1(); int test2(); int main(int argc, const char *argv[]) { std::cout << is_void<decltype(test1())>::value << std::endl; // 1 std::cout << is_void<decltype(test2())>::value << std::endl; // 0 return 0; }
有了判断void的思路基础,不难写出判断其他类型的,比如说判断是否为浮点数,那么只需要对float,double,long double进行特殊处理即可,请看代码:
template <typename> struct is_floating_point : false_type {}; template <> struct is_floating_point<float> : true_type {}; template <> struct is_floating_point<double> : true_type {}; template <> struct is_floating_point<long double> : true_type {};
整型判断相对复杂一点,需要对char,signed char,unsigned char,short,unsigned short,int,unsigned,long,unsigned long,long long,unsigned long long都进行特例编写,方法相同,不再赘述。
类型处理
在上一节编写is_floating_point的时候可能会发现这样的问题:
int main(int argc, const char *argv[]) { std::cout << is_floating_point<const double>::value << std::endl; // 0 std::cout << is_floating_point<double &>::value << std::endl; // 0 return 0; }
但是照理来说,const类型以及引用类型不应该影响他浮点数的本质,当然,我们也可以针对所有的const以及引用情况都编写模板特例,但这样太麻烦了,如果有办法可以去掉const以及引用这些符号,然后再去判断的话,就会减少我们很多工作量。与此同时,这样的类型处理在实际编程时也是很有用的。
那么,如何去掉const?请看代码:
template <typename T> struct remove_const { using type = T; }; template <typename T> struct remove_const<const T> { using type = T; };
同样的思路,当T是const类型时,我们变换成const T,然后只取出T,其他类型时直接透传T。
同理,用这种方法也可以去除引用:
template <typename T> struct remove_reference { using type = T; }; template <typename T> struct remove_reference<T &> { using type = T; }; template <typename T> struct remove_reference<T &&> { using type = T; };
因此,is_floating_point就可以改写成这样:
// 基础判断降级为helper template <typename> struct is_floating_point_helper : false_type {}; template <> struct is_floating_point_helper<float> : true_type {}; template <> struct is_floating_point_helper<double> : true_type {}; template <> struct is_floating_point_helper<long double> : true_type {}; // remove_reference和remove_const的声明 template <typename> struct remove_const; template <typename> struct remove_reference; // 实际的is_floating_point template <typename T> struct is_floating_point : is_floating_point_helper<typename remove_const<typename remove_reference<T>::type>::type> {};
类型选择
我们搞这样一系列的类型封装,最主要的原因是为了在编译器进行逻辑判断。因此,必然要进行一个选择逻辑,也就是当条件成立时,选择某一个类型,不成立时选择另一个类型。这个功能非常好实现,请看代码:
template <bool judge, typename T1, typename T2> struct conditional { using type = T1; }; template <typename T1, typename T2> struct conditional<false, T1, T2> { using type = T2; };
当第一个参数为true时,type就与T1相同,否则就与T2相同。
判断是否相同
我们有时候还需要判断两个类型是否相同,这部分也很好实现,请看代码:
template <typename, typename> struct is_same : false_type {}; template <typename T> struct is_same<T, T> : true_type {};
tips
其实按照这些逻辑,我们几乎可以写出type_traits中的所有功能了。STL中还实现了合取、析取、取反等操作,只是将逻辑判断转为了模板形式,这些用起来更方便,但不是必须的。大家感兴趣可以阅读这部分源码。
实现is_base_of
is_base_of用于判断两个类型是否是继承关系,在C++中已经存在了对应的关键字用于判断:
struct B {}; struct D : B {}; struct A {}; int main(int argc, const char *argv[]) { std::cout << __is_base_of(B, D) << std::endl; // 1 std::cout << __is_base_of(B, A) << std::endl; // 0 return 0; }
__is_base_of关键字就可以完成这样的工作,所以我们封装它为模板即可:
template <typename B, typename D> struct is_base_of : conditional<__is_base_of(B, D), true_type, false_type> {};
但除了这种直接使用编译器提供的关键字外,这个功能还有一种其他的实现方法。
如何判断一个类是否为一个类的父类呢?其实就看指针能否转换(多态)即可。请看代码:
template <typename B, typename D> true_type test_is_base(B *); template <typename B, typename D> false_type test_is_base(void *); template <typename B, typename D> struct is_base_of : decltype(test_is_base<B, D>(static_cast<D *>(nullptr))) {};
如果D是B的子类,那么就会调用第一个函数,从而推断出返回值是true_type,否则调用第二个函数,推断出返回值是false_type。
不过这样做还必须加一个判断,就是B和D必须都是类才行,而且需要去掉const等因素,详细代码读者可以自行尝试,不再赘述。
到此这篇关于C++超详细分析type_traits的文章就介绍到这了,更多相关C++ type_traits内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!