亲宝软件园·资讯

展开

C++ type_traits

fl2011sx 人气:0

本篇文章旨在引导大家自行实现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等因素,详细代码读者可以自行尝试,不再赘述。

加载全部内容

相关教程
猜你喜欢
用户评论