我的C++实践(9):一个完整的类型区分框架

    (1)辨别基本类型:IsFundaT及MK_FUNDA_TYPE(T)。
    独特构造:基本类型都是已知的,并且个数有限,因此直接用特化来实现。定义一个基本模板表示非基本类型,然后结合宏MK_FUNDA_TYPE(T)来对各个基本类型提供特化,以表示它们是基本类型,用宏主要是为了使代码更简洁。

//isfundat.hpp:辨别基本类型
#ifndef IS_FUNDAT_HPP
#define IS_FUNDAT_HPP
template 
class IsFundaT { //基本模板:表示非基本类型
public:enum { Yes = 0, No = 1};
};
// 用于特化基本类型的宏
#define MK_FUNDA_TYPE(T)         /
template<> class IsFundaT {   /
public:                          /enum { Yes = 1, No = 0 };    /
};
MK_FUNDA_TYPE(void)
MK_FUNDA_TYPE(bool)
MK_FUNDA_TYPE(char)
MK_FUNDA_TYPE(signed char)
MK_FUNDA_TYPE(unsigned char)
MK_FUNDA_TYPE(wchar_t)
MK_FUNDA_TYPE(signed short)
MK_FUNDA_TYPE(unsigned short)
MK_FUNDA_TYPE(signed int)
MK_FUNDA_TYPE(unsigned int)
MK_FUNDA_TYPE(signed long)
MK_FUNDA_TYPE(unsigned long)
#if LONGLONG_EXISTS  //如果编译器支持long long类型MK_FUNDA_TYPE(signed long long)MK_FUNDA_TYPE(unsigned long long)
#endif
MK_FUNDA_TYPE(float)
MK_FUNDA_TYPE(double)
MK_FUNDA_TYPE(long double)
#undef MK_FUNDA_TYPE
#endif

    (2)辨别函数类型:IsFunctionT
    独特构造:数组元素的类型不能为void类型、引用类型或函数类型。因此可构造一个测试用的成员函数模板,函数形参为模板参数的数组类型指针,这样数组元素就不能接受函数类型。同时提供该成员另一个重载版本,以应付T是函数类型的情况(SFINAE原则),对有干扰的void类型和引用类型提供特化以表示它们不是函数类型,这样就可以辨别T了。

//isfunctiont.hpp:辨别函数类型
#ifndef IS_FUNCTIONT_HPP
#define IS_FUNCTIONT_HPP
template
class IsFunctionT {
private:typedef char One;typedef struct { char a[2]; } Two;template static One test(...); //U为函数时使用这个,只需声明,无需定义template static Two test(U (*)[1]); //U为非函数、非引用及非void类型时使用这个
public:enum { Yes = sizeof(test(0)) == 1 }; //记录测试的结果enum { No = !Yes };
};
template
class IsFunctionT { //T是引用类型时会使用这个局部特化,表示它不是函数类型
public:enum { Yes = 0 };enum { No = !Yes };
};
template<>
class IsFunctionT { //T是void类型时会使用这个全局特化,表示它不是函数类型
public:enum { Yes = 0 };enum { No = !Yes };
};
template<>
class IsFunctionT { //T是void const类型时会使用这个全局特化
public:enum { Yes = 0 };enum { No = !Yes };
};
// 对于void volatile和void const volatile类型也是一样
//...
#endif

    代码中的U (*)[1]是一个指向数组U[1]的指针,U是数组元素的类型。可见这里的U不能为函数、引用及void类型。当U为函数、引用及void以外的其他类型时,sizeof就会使用这个版本的test函数,返回的Two的字节数2,则Yes为0,表示非函数类型。当为引用或void类型时,会使用特化版本,设置Yes为0。当为函数类型时,sizeof中的Test匹配有数组指针的那个Test时不会成功,但它能匹配另一个返回One为1个字节的test版本,根据SFINAE原则,它会使用这个版本的test,Yes设置为1。注意这里test只需声明,无需定义,因为sizeof并不会真正调用并执行该函数,不需要函数代码定义,它只是计算返回类型的字节数。
    当然,我们还可以用其他的独特构造来实现,比如只有对函数类型,F&(指向函数类型的引用)才能转化为F*。通过判断能否把一个F&转化为F*,也可辨别出F是否是函数类型。
    (3)辨别复合类型:CompoundT::IsPtrT,IsRefT,IsArrayT,IsFuncT,IsPtrMemT,这包括指针类型、引用类型、数组类型、函数类型、成员指针类型。
    独特构造:这些类型局部已知,且个数有限,可直接用局部特化来实现。对函数类型则可复用上面的IsFunctionT

//compoundt.hpp:辨别复合类型,这包括指针类型、引用类型、数组类型、函数类型、成员指针类型
#ifndef COMPOUNDT_HPP
#define COMPOUNDT_HPP
#include 
#include "isfunctiont.hpp"
template
class CompoundT {  //基本模板
public:enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,IsFuncT = IsFunctionT::Yes,IsPtrMemT = 0 };typedef T BaseT; //T直接依赖的类型typedef T BottomT; //去除所有复合依赖后最底层的那个裸类型typedef CompoundT ClassT; //T所属的类(用在成员指针类型中)
};
template
class CompoundT {       //对引用类型的局部特化
public:enum { IsPtrT = 0, IsRefT = 1, IsArrayT = 0,IsFuncT = 0, IsPtrMemT = 0 };typedef T BaseT;typedef typename CompoundT::BottomT BottomT;typedef CompoundT ClassT;
};
template
class CompoundT {       //对指针类型的局部特化
public:enum { IsPtrT = 1, IsRefT = 0, IsArrayT = 0,IsFuncT = 0, IsPtrMemT = 0 };typedef T BaseT;typedef typename CompoundT::BottomT BottomT;typedef CompoundT ClassT;
};template
class CompoundT  {    //对数组类型的局部特化
public:enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 1,IsFuncT = 0, IsPtrMemT = 0 };typedef T BaseT;typedef typename CompoundT::BottomT BottomT;typedef CompoundT ClassT;
};
template
class CompoundT  {    //对空数组类型的局部特化
public:enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 1,IsFuncT = 0, IsPtrMemT = 0 };typedef T BaseT;typedef typename CompoundT::BottomT BottomT;typedef CompoundT ClassT;
};
template
class CompoundT  {  //对成员指针类型的局部特化
public:enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,IsFuncT = 0, IsPtrMemT = 1 };typedef T BaseT;typedef typename CompoundT::BottomT BottomT;typedef C ClassT;
};
//
//下面几个是针对给定参数个数的函数类型局部特化,不提供也没问题,因为上面使用了IsFunctionT
//提供了还可以访问它们的返回类型和参数类型
template
class CompoundT {  //对无参数的函数类型的局部特化,R是函数的返回类型
public:enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,IsFuncT = 1, IsPtrMemT = 0 };typedef R BaseT();typedef R BottomT();typedef CompoundT ClassT;
};
template
class CompoundT {  //对单参数的函数类型的局部特化
public:                 //R是返回类型,P1是参数类型enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,IsFuncT = 1, IsPtrMemT = 0 };typedef R BaseT(P1);typedef R BottomT(P1);typedef CompoundT ClassT;
};
template
class CompoundT {  //对不少于一个参数的函数类型的局部特化
public:enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,IsFuncT = 1, IsPtrMemT = 0 };typedef R BaseT(P1);typedef R BottomT(P1);typedef CompoundT ClassT;
};
//还可以定义其他给定参数个数的函数类型局部特化#endif

    这里还对给定参数个数的函数类型定义了局部特化。这些特化不提供也没问题,因为代码中是使用上面的IsFunctionT来辨别函数类型的。但提供这些特化也不冲突,提供它们的好处是我们还可以访问函数类型中的参数类型和返回类型。从中我们也可以看出,不能通过直接的局部特化来辨别一般的函数类型,因为函数的参数个数不确定,这样就需要为每种参数个数的函数类型都提供局部特化,会有无数个局部特化。
    (4)辨别类类型(class,struct,union):IsClassT
    独特构造:只有类类型才有成员指针。因此可构造一个测试用的成员函数模板,函数形参为模板参数的一个成员指针,这样模板参数就只能为类类型了。同时提供该成员另一个重载版本,以应付T是非类类型时的情况(SFINAE原则)。

//isclasst.hpp:辨别类类型(class,struct,union)
#ifndef IS_CLASST_HPP
#define IS_CLASST_HPP
template
class IsClassT{  //确定某个类型是否为类类型
private:typedef char One;typedef struct{char a[2];} Two;templatestatic One test(int C::*); //C是类类型时使用这个版本,函数参数是一个C的成员指针templatestatic Two test(...); //C是非类类型时使用这个版本
public:enum { Yes=sizeof(test(0))==1 }; //是类类型则Yes为1,不是类类型时Yes为0enum { No=!Yes };
};
#endif

    (5)辨别枚举类型。IsEnumT。一种实现是排除上面所有的类型,剩下的就是枚举类型了。我们也可以针对枚举类型的独特构造来提供另一种实现。
    独特构造:枚举类型可以隐式转型为整型,这需要排除其他能够转型为整型的基本类型、指针类型、引用类型、成员指针类型的干扰。可用特化,也可用重载解析来实现。定义一个可转型为模板参数的类模板,和一个测试用的函数,为各种受干扰的类型提供该函数的重载版本。通过传递一个可转型的对象给测试函数,转型为模板参数T后,就会运用重载解析来调用相应的重载版本,以辨别出枚举类型。

//isenumt.hpp:辨别枚举类型,运用类型转换和重载解析来实现
#ifndef IS_ENUMT_HPP
#define IS_ENUMT_HPP
#include "isfundat.hpp"
#include "compoundt.hpp"
struct SizeOverOne { char c[2]; }; //用作返回类型
template::IsFuncT &&!CompoundT::IsArrayT>
class ConsumeUDC {  //基本模板:T为非函数类型非数组类型时,ConsumeUDC允许转型为Tpublic:operator T() const; //转型运算符:转型为T
};
template 
class ConsumeUDC {//对非函数类型非数组类型的特化:ConsumeUDC不允许转型为T
};
template 
class ConsumeUDC { //对void类型的特化:不允许转型
};
//下面的enum_check函数只需声明,无需定义,因为sizeof运算并不会真正调用该函数
char enum_check(bool);
char enum_check(char);
char enum_check(signed char);
char enum_check(unsigned char);
char enum_check(wchar_t);
char enum_check(signed short);
char enum_check(unsigned short);
char enum_check(signed int);
char enum_check(unsigned int);
char enum_check(signed long);
char enum_check(unsigned long);
#if LONGLONG_EXISTS  //如果编译器支持long long类型char enum_check(signed long long);char enum_check(unsigned long long);
#endif
//避免从float到int的意外转型
char enum_check(float);
char enum_check(double);
char enum_check(long double);
SizeOverOne enum_check(...);    //捕获剩余的所有情况
template
class IsEnumT { //辨别枚举类型public:enum { Yes = IsFundaT::No &&!CompoundT::IsRefT &&!CompoundT::IsPtrT &&!CompoundT::IsPtrMemT &&sizeof(enum_check(ConsumeUDC()))==1 };enum { No = !Yes };
};
#endif

    IsEnumT中,先排除T是基本类型、引用类型、指针类型、成员指针类型的情况。在sizeof运算中,enum_check接受一个ConsumeUDC的临时对象作为参数,由于ConsumeUDC中自定义了转型运算符,因此它会转型为一个T型对象,但T如果是函数类型、数组类型或void类型,则不允许转型(即调用其中的特化版本),这样根据重载解析,enum_check将会选择最后的那个省略号版本,sizeof返回2,Yes设置为0。剩下的只有类类型和枚举类型了。如果T是类类型,则允许转型,转型过来的T型对象只能匹配那个省略号的test版本,Yes被设置为0。注意即使T自定义了一个到整型的转型运算符,也不会再考虑,因为在重载解析的匹配中,只允许有一次的自定义转型。最后,当T是枚举类型时,ConsumeUDC对象自定义转型为T型对象,然后T会类型提升为整型(这个是自动的隐式转型,不是自定义的),从而enum_check匹配一个整型参数的版本,sizeof返回1,Yes设置为1。可见,这个IsEnumT实现比较复杂,这里主要是为了展示各种高级C++语法特性的应用,包括重载解析、模板特化、类型转换(自定义的转型及隐式的类型提升)等。
    (5)完整的类型区分实现。

//typet.hpp:完整的类型区分实现
#ifndef TYPET_HPP
#define TYPET_HPP
#include "isfundat.hpp"
#include "compoundt.hpp"
#include "isclasst.hpp"
#include "isenumt.hpp"
template 
class TypeT { //辨别所有类型的模板
public:enum { IsFundaT  = IsFundaT::Yes,IsPtrT    = CompoundT::IsPtrT,IsRefT    = CompoundT::IsRefT,IsArrayT  = CompoundT::IsArrayT,IsFuncT   = CompoundT::IsFuncT,IsPtrMemT = CompoundT::IsPtrMemT,IsEnumT   = IsEnumT::Yes,IsClassT  = IsClassT::Yes };
};
#endif // TYPET_HPP

    (6)测试代码。

//typestest.cpp:类型区分的测试
#include "typet.hpp"
#include 
class MyClass { //类类型
};
void myfunc(){ //函数类型
}
enum E { e1 }; //枚举类型
template 
void check(){  //检查传进来的模板实参类型if (TypeT::IsFundaT) {std::cout << " IsFundaT ";}if (TypeT::IsPtrT) {std::cout << " IsPtrT ";}if (TypeT::IsRefT) {std::cout << " IsRefT ";}if (TypeT::IsArrayT) {std::cout << " IsArrayT ";}if (TypeT::IsFuncT) {std::cout << " IsFuncT ";}if (TypeT::IsPtrMemT) {std::cout << " IsPtrMemT ";}if (TypeT::IsEnumT) {std::cout << " IsEnumT ";}if (TypeT::IsClassT) {std::cout << " IsClassT ";}std::cout << std::endl;
}
template 
void checkT (T a){ //检查传进来的函数调用实参类型check();//对指针类型,检查它们所依赖的类型if (TypeT::IsPtrT || TypeT::IsPtrMemT) {check::BaseT>(); //BaseT为T所依赖的类型}
}
int main(){std::cout << "int:" << std::endl;check();std::cout << "int&:" << std::endl;check();std::cout << "char[42]:" << std::endl;check();std::cout << "MyClass:" << std::endl;check();std::cout << "ptr to enum:" << std::endl;E* ptr = 0; //指向枚举类型的指针checkT(ptr);std::cout << "42:" << std::endl;checkT(42);std::cout << "myfunc():" << std::endl;checkT(myfunc); //myfunc会退化为指针,它所依赖的类型为函数类型void()std::cout << "memptr to array:" << std::endl;char (MyClass::* memptr) [] = 0; //指向char型空数组的成员指针checkT(memptr);return 0;
}

转载于:https://my.oschina.net/abcijkxyz/blog/722718


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部