C++下typeof的实现

使用该方法可实现各大类型的id索引

Posted by VK on August 8, 2019

[转]C++ 下 typeof 的实现

现在我们有这样一个代码:

std::vector<int> arr;  
// ...  
for(std::vector<int>::iterator iter = arr.begin(); iter != arr.end(); ++iter)  
{  
	// ...  
}  

其中难看而又不好维护的std::vector::iterator,由于我们无法自动获知arr.begin()的类型,从而不得不一写再写。

C++11下有typeof和auto关键字,于是像上面第3行那样纠结的位置可以变得简单不少:

std::vector<int> arr;  
// ...  
for(auto iter = arr.begin(); iter != arr.end(); ++iter)  
{  
     // ...  
}  

在vc下(2005、2008、2010)对这两个关键字都不支持;gcc(4.7以前)支持typeof,但是没有auto。

假如有typeof的话,auto可以很简单的模拟出来,那么问题的关键点在于如何实现typeof

一、需要注册id的typeof

在C++里,可以在编译期计算表达式类型的只有下面两个东西:

1. sizeof ​ 这东西很强大,不论后面的表达式是什么,均可以在编译期正确得到类型并直接返回类型大小。 2. typeid ​ 若不使用C++的RTTI功能,typeid会在编译期计算出表达式的类型,并返回一个type_info引用。

使用第一种方法,我们可以得到一个数字,只要这个数字对类型而言是唯一的,那么我们就可以通过它反向得到类型。 类似这样写:

template <typename T>  
struct Type2ID;  
template <int id>  
struct ID2Type;  
   
template <typename T>  
Type2ID<T> encode(const T&);  
template <typename T>  
Type2ID<T> encode(T&);  
   
#define type_of(...) \  
		ID2Type<sizeof(encode((__VA_ARGS__)))>::type_t  

之所以用可变参数宏,是考虑到需要能够支持传入如逗号表达式等带逗号的参数。

第6-9行定义了假函数encode,负责把__VA_ARGS__的类型取出来。sizeof运算符保证了encode并不会真正被执行到。

为了让我们前面的代码可以工作,还需要使用模板特化的机制注册一些类型:

#define REGISTER_TYPE(type, n) \  
template <> \  
struct Type2ID<type> { char id[n]; }; \  
template <> \  
struct ID2Type<n>    { typedef type type_t; };  
   
REGISTER_TYPE(int,    1)  
REGISTER_TYPE(long,   2)  
REGISTER_TYPE(char,   3)  
REGISTER_TYPE(double, 4)  

现在我们可以支持int、long、char和double表达式甚至其他结构体类型的动态类型推导了。

例子:

/*************************************************************************
	> File Name: typeof.cpp
	> Author: Veitch
	> Mail: 648974975@qq.com 
	> Created Time: 2019年08月08日 星期四 10时09分57秒
 ************************************************************************/

#include<iostream>
using namespace std;
#include <stdio.h>
#include <string.h>

template <typename T>  
struct Type2ID;  
template <int id>  
struct ID2Type;  
  
//template <typename T>  
//Type2ID<T> encode(const T&);  
//template <typename T>  
//Type2ID<T> encode(T&);  
  
#define type_of(n)\
    ID2Type<n>::type_t  



#define REGISTER_TYPE(type,n)\
    template <>\
	struct Type2ID<type> { char id[n]; };\
    template <>\
	struct ID2Type<n>    { typedef type type_t; };  
  
REGISTER_TYPE(int,    1)  
REGISTER_TYPE(long,   2)  
REGISTER_TYPE(char,   3)  
REGISTER_TYPE(double, 4)  


typedef struct Info
{
	char data[10];
	int index;
}Info_t;



REGISTER_TYPE(Info_t, 5)

int main(void){
	ID2Type<3>::type_t c = 'A';
  	ID2Type<4>::type_t m = 3.15;
	ID2Type<5>::type_t info;
	info.index = 3;
	memcpy(info.data,"hahahwehw",10);
//	typeof(4) mydouble = 3.123;
	cout<<c<<endl;
	printf("m:%f\n",m);	
	printf("info.index:%d, info.data:%s\n",info.index,info.data);

	return 0;
}

二、自动分配id的typeof

目前我们实现的type_of虽然可以工作,但在干活之前必须要先注册一大堆类型,而且还需要给每个类型分配一个唯一的id。没有注册的类型是无法动态推导的。 boost里使用了mpl库里的一些东西,完成了REGISTER_TYPE的过程,自动化的给每个放入BOOST_TYPE_OF里的类型分配了唯一的id。我们手头上没有这么NX的东西,只好使用一些轻量级的玩意了。

比如上面提到的typeid,就是个不错的id生成器,也具有sizeof类似的功能。 虽然多态类继承的情况会让typeid在编译期失效,但只限于传入对象的情况。我们可以使用指针规避这个问题,typeid将在编译期返回一个指针类型的type_info。

下面我们开始实作可以自动分配id的typeof。首先,我们需要一个可以把type_info&变成类型的玩意:

template <const std::type_info& type_id>  
struct TypeID {};  

#define type_id(...) \
	TypeID<typeid(__VA_ARGS__)>  

然后是从TypeID中把type取出来的类模板:

/* 
Extract a type from TypeID 
*/  
   
template<typename ID>  
struct Extract;  
   
#define type_extract(...) \  
     Extract<type_id(__VA_ARGS__) >::type_t  

把类型编码成待注册的类型:

/* 
     Encode a expression 
*/  
   
template <typename T>  
struct Encode { typedef T* type_t; };  
   
template <typename T>  
typename Encode<T>::type_t encode(const T&);  
template <typename T>  
typename Encode<T>::type_t encode(T&);  

这里使用了和先前同样的encode假函数技巧,同时通过Encode把一个类型变成一个类型指针。

最后把类型解出来:

/* 
     Decode a type 
*/  
   
template <typename T>  
struct Decode;  
template <typename T>  
struct Decode<T*> { typedef T type_t; };  
   
#define type_of(...) \  
     Decode<type_extract(encode((__VA_ARGS__)))>::type_t  

这里使用Decode把Encode变化了的类型变回来。

使用Encode、Decode的目的,是为了绕过typeid对对象可能进行的运行时处理。

不要妄想C++的模板自动推导可以这样写:

template<typename T>  
struct Extract<TypeID<typeid(T*)> >  
{  
     typedef T* type_t;  
};  

编译器会无情的告诉我们一个error发生了。假如可以这样自动推导,那我们接下来会省很多力气。

为了让Extract能够认得传入的类型信息,我们得注册我们的类型,但是不再需要传入id了:

#define REGISTER_TYPE(type) \  
     template<> \  
     struct Extract<type_id(type*) > { typedef type* type_t; };  
   
REGISTER_TYPE(int)  
REGISTER_TYPE(long)  
REGISTER_TYPE(char)  
REGISTER_TYPE(double)  

可以测试一下,typeid是否能完成编译期的类型自动推导。为了说明问题,代码使用了多态的对象来测试效果:

class A
{
public:
  virtual void func(){
    std::cout<<"A"<<std::endl;
  }  	
};

class B:public A
{
public:
	virtual void func(){std::cout<<"B"<<std::endl;}
};

REGISTER_TYPE(A)
REGISTER_TYPE(B)

  
int main()
{
  B bb;
  A& aa = bb;
  type_of(aa) cc;
  cc.func();
  
  return 0;
}

在vc编译器(2005、2008、2010、2012)下编译后,我们会顺利的得到一个喜闻见乐的“A”。


三、全自动的typeof

虽然仅仅写一行REGISTER_TYPE已经不是什么大问题了,但多写这一行还是一个让人不爽的事情。 想要实现全自动注册,得解决一个麻烦的问题:如何通过const type_info&特化模板?

幸好vc里还有一些小花招可以利用,我们可以尝试在一个类的内部特化一个类模板:

/* 
     Extract a type from TypeID 
*/  
   
struct empty_t {};  
   
template<typename ID, typename T = empty_t>  
struct Extract;  
   
template<typename ID>  
struct Extract<ID, empty_t>  
{  
     template <bool>  
     struct id2type;  
};  
   
template<typename ID, typename T>  
struct Extract : Extract<ID, empty_t>  
{  
     template <>  
     struct id2type<true> { typedef T type_t; };  
};  
   
#define type_extract(...) \  
     Extract<type_id(__VA_ARGS__) >::id2type<true>::type_t  

这个花招的“精髓”在于,类模板的内部又有一个类模板,并且使用继承让“特化在特化中生效”。这种玩法在gcc中是编译不过的,它会抱怨你正在试图特化一个位于非全局且非namespace区域的类模板。不过不要紧,只要vc能用就可以了。

这样我们就可以写出一个专门用于注册的类模板:

/* 
     Register a type 
*/  
   
template<typename T, typename ID>  
struct Register : Extract<ID, T>  
{  
    typedef typename id2type<true>::type_t type_t;  
};  

它将通过继承的Extract自动特化出一个存有类型信息的id2type。

后面的事情就简单了,只需要稍微改写一下Encode:

template <typename T>  
struct Encode  
{  
     typedef T* enc_type_t;  
     typedef Register<enc_type_t, type_id(enc_type_t)> reg_type;  
     typedef typename reg_type::type_t type_t;  
};  

现在的type_of可以直接作用于任何表达式,并在编译时自动推导出类型了。

扫尾工作:

区别对待vc和gcc,并定义auto:

#ifdef GNUC  
#define type_of(...) typeof((VA_ARGS__))  
#else  
#define type_of(...) \  
     Decode<type_extract(encode((__VA_ARGS__)))>::type_t  
#endif  
#define auto(name, ...) type_of(VA_ARGS) name((VA_ARGS))  

必须要记得我们前面写的那些类模板也只能在vc下工作,需要一并放入#else段里。


相关内容请参考:

http://svn.boost.org/svn/boost/trunk/boost/typeof/msvc/typeof_impl.hpp

文章代码请参考:

http://neonx.googlecode.com/svn/trunk/neoncore/type/typeof.h

更多内容请访问:

http://darkc.at

http://blog.csdn.net/markl22222/article/details/10474591