C++模板元编程中,编译器对模板的特化版本选择相当于是在做模式匹配,这个我们已经比较熟悉了。下面我们借助这一特性实现一个在模板元编程中最常使用的基础元函数IfThenElse
,使用它可以完成类型选择的功能。
template<typename Condition, typename Then, typename Else> struct IfThenElse;
template<typename Then, typename Else>
struct IfThenElse<TrueType, Then, Else>
{
using Result = Then;
};
template<typename Then, typename Else>
struct IfThenElse<FalseType, Then, Else>
{
using Result = Else;
};
#define __if(...) typename IfThenElse<__VA_ARGS__>::Result
有了IfThenElse,就可以轻易地完成根据条件进行类型选择的计算。如下我们借助IfThenElse实现了一个元函数LargerType,它能够返回两个类型中内存空间更大的那个。
template<typename T, typename U>
using LargerType = __if(__bool(sizeof(T) > sizeof(U)), T, U);
除了模板特化,还有一个工具可以用来在模板元编程中完成模式匹配的功能,那就是C++编译器对重载函数的选择。
我们通过下面的示例展示如何通过函数重载来完成模式匹配。
我们知道C++中某些类型之间支持默认转型。例如short默认可以向int转型,子类指针可以默认转型为父类指针,而任何指针类型都可以默认转型为void*
类型。下面我们将实现一个元函数,它能帮我们识别一个类型是否能够向另一个类型默认转型。
通过分析,我们定义这个元函数的的原型为:
IsConvertible :: (typename T -> typename U) -> BoolType
它的入参是两个类型T和U,如果T可以默认转型为U,则元函数返回BoolType<true>
,否则返回BoolType<false>
。
如下我们借助编译器对重载函数的选择来完成模式匹配,以实现IsConvertible
。
// “tlp/traits/IsConvertible.h”
template<typename T, typename U>
struct IsConvertible
{
private:
using Yes = char;
struct No { char dummy[2]; };
static Yes test(U);
static No test(...);
static T self();
public:
using Result = BoolType<sizeof(test(self())) == sizeof(Yes)>;
};
#define __is_convertible(...) typename IsConvertible<__VA_ARGS__>::Result
上面代码中,我们在IsConvertible中定义了静态函数test的两个重载版本,一个入参类型是U,另一个是随意类型入参(...
出现在C++函数参数声明中表示不关心入参类型)。然后我们尝试把T传入test函数,如果T能够向U转型,则编译期会选择Yes test(U)
版本,否则选择No test(...)
版本。最后我们计算test返回类型的sizeof,就能判断出编译器选择了哪个版本(Yes和No是IsConvertible内部定义的两个类型,Yes的sizeof结果是1个字节,No是两个字节;sizeof是一个编译期运算符)。
在上面的实现中我们用了一个小技巧,我们并没有给test直接传入T的对象,因为这样做的话我们就要承受让T生成对象的开销,而且关键的是我们对T的构造函数一无所知。所以这里声明了一个返回类型为T的静态函数static T self()
,然后把这个函数交给test。还记得我们前面说的“一切都是函数,一切都是类型”吗?我们用self函数替代类型T的对象传入test,在编译期就能获得结果,而且避免了创建对象的开销。
借助__is_convertible
我们能够轻易的实现一个判断两个类型是否能够互相转型的元函数。
// “tlp/traits/IsConvertible.h”
#define __is_both_convertible(T, U) __and(__is_convertible(T, U), __is_convertible(U, T))
上面代码中的__and()
是我们前面介绍的对两个BoolType进行逻辑与运算的元函数。
现在我们可以这样使用:
__is_convertible(char, int) // 返回__true()
__is_convertible(char, void*) // 返回__false()
__is_convertible(char*, void*)// 返回__true()
struct Base{};
struct Derived : Base {};
__is_convertible(Base*, Derived*) // 返回__false()
__is_convertible(Derived*, Base*) // 返回__true()
通过函数组合,我们还能实现出__is_base_of()
,用来判断一个类型是否是另一个的父类。
// "tlp/traits/IsBaseOf.h"
#define __is_base_of(T, U) \
__and(__is_convertible(const U*, const T*) \
__and(__not(__is_eq(const T*, const void*)), \
__not(__is_eq(const T, const U))))
如上我们定义类型T是类型U的父类的意思就是:const U*
可以向const T*
转型,但是const T*
不是const void*
,同时const T
和const U
不是相同类型。
网友评论