譬如有一个类型T,我们需要知道他是不是整数(int,short,char,long等等),有两种实现方式:
第一种应该是比较容易想到的,利用模板偏特化实现。
template<class T>
struct My_Is_Integer
{
using type = std::false_type;
};
template<>
struct My_Is_Integer<int>
{
using type = std::true_type;
};
template<>
struct My_Is_Integer<short>
{
using type = std::true_type;
};
测试代码如下:
bool bIsInt = My_Is_Integer<int>::type::value; //bIsInt为true
但是这种方式有个缺点,就是如果我们要判断该类型是不是浮点数,又得重新定义结构体,不够灵活。所以考虑能不能利用模板的可变模板参数来实现。这就是第二种方式。
譬如我们先写一个如下的模板类:
template<class T, class... Rest>
struct Is_Include;
这里面T是我们的类型,后面Rest可以写我们期望的类型,只要T是Rest中一个即可。这里的重点就是如何让T与Rest里的参数进行比较。这里需要用到一个可变模板参数展开的技巧。我们知道std::is_same是可以判断两个类型是否相等的,那么我们可以让T与Rest每一个参数构造一个std::is_same<T,Rest>,然后引入另外一个模板类进行承接:
template<class First,class... Rest>
struct Is_Include_1;
template<class T, class... Rest>
struct Is_Include : Is_Include_1<std::is_same<T, Rest>...>{};
这里用到了可变模板参数的展开,利用std::is_same<T,Rest>...将T,Rest...转换为另外一组类型:std::is_same<T,Rest1>, std::is_same<T,Rest2>, std::is_same<T,Rest3>...我们引入另一个类_Is_Include_1来承接这些类型,这些类型有相同的接口和字段,并且存在编译器的字段value, 这个特征很重要,后面需要用。
目前的问题转化为我们有一个模板类Is_Include_1,模板参数类型都是std::is_same,判断这些模板参数类型里有没有一个字段value为true,既然这些模板参数类型都有value字段,那么我们可以把这个参数提取出来作为一个模板参数,因此我们需要引入第三个类Is_include_2, 让Is_Include_1继承Is_Include_2,在继承的过程中将First::value作为Is_include_2的第一个模板参数:
template<bool bSame, class First, class... Rest>
struct Is_Include_2
{
using type = First;
};
template<class First,class... Rest>
struct Is_Include_1 : Is_Include_2<First::value,First,Rest...>{ };
第三个类的第一个模板参数可以是First:value,这个时候可以引入模板偏特化,对bSame进行偏特化处理:
template<class First, class Next, class... Rest>
struct Is_Include_2<false, First, Next, Rest...> : Is_Include_2<Next::value, Next, Rest...>
{};
如果第一个参数First:value为false,则与偏特化类吻合,然后通过继承来实现可变参数类型的展开,每次展开就是让下一个参数的value也就是Next:value作为第一个参数,Next作为第二个参数,只要Next:value为true,那么就会采用非偏特化的模板类,此时就终止了继承,否则通过继承以相同的方式处理下一个类型,然后我们把第二个参数作为type即可。这样子我们就得到value为true的std::is_same了。
这里面还有个问题需要注意:如果所有的std::is_same的value都是false,那么是不是永远采用偏特化模板来实例化,那么继承不就是陷入死循环了吗。我们可以注意到如果所有的std::is_same的value都是false,那么当继承到只剩下最后一个参数时,Is_Include_2的模板参数类型有两个了,一个是Next::value,一个是Next,后面就没有了。我们注意到偏特化模板类需要至少三个模板参数,所以即便Next::value为false,也会采用非偏特化的模板类实例化,此时type就等于最后一个std::is_same了
最后完整代码如下:
template<bool bSame, class First, class... Rest>
struct Is_Include_2
{
using type = First;
};
template<class First, class Next, class... Rest>
struct Is_Include_2<false, First, Next, Rest...> : Is_Include_2<Next::value, Next, Rest...>{};
template<class First,class... Rest>
struct Is_Include_1 : Is_Include_2<First::value,First,Rest...>{ };
template<class T, class... Rest>
struct Is_Include : Is_Include_1<std::is_same<T, Rest>...>{};
然后我们再根据需要进行一些处理,使得更好用:
template<class T>
constexpr bool is_int = Is_Include<T,int,short,long>::type::value;//判断是否为整数
template<class T>
constexpr bool is_float = Is_Include<T,float,double>::type::value;//判断是否为浮点数
第二种方式虽然复杂一些,但是后续扩展非常优雅,第二种方式也是C++里_Is_any_of_v的实现思路,算是做了一次源码解读吧。