C++ 下 typeof 的实现

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

[cpp] view plaincopy

  1. std::vector<int> arr;
  2. // ...
  3. for(std::vector<int>::iterator iter = arr.begin(); iter != arr.end(); ++iter)
  4. {
  5. // ...
  6. }

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

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

[cpp] view plaincopy

  1. std::vector<int> arr;
  2. // ...
  3. for(auto iter = arr.begin(); iter != arr.end(); ++iter)
  4. {
  5. // ...
  6. }

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

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


一、需要注册id的typeof

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

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

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

[cpp] view plaincopy

  1. template <typename T>
  2. struct Type2ID;
  3. template <int id>
  4. struct ID2Type;
  5. template <typename T>
  6. Type2ID<T> encode(const T&);
  7. template <typename T>
  8. Type2ID<T> encode(T&);
  9. #define type_of(...) \
  10. ID2Type<sizeof(encode((__VA_ARGS__)))>::type_t

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

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

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

[cpp] view plaincopy

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

现在我们可以支持int、long、char和double表达式的动态类型推导了。


二、自动分配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&变成类型的玩意:

[cpp] view plaincopy

  1. template <const std::type_info& type_id>
  2. struct TypeID {};
  3. #define type_id(...) TypeID<typeid(__VA_ARGS__)>

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

[cpp] view plaincopy

  1. /*
  2. Extract a type from TypeID
  3. */
  4. template<typename ID>
  5. struct Extract;
  6. #define type_extract(...) \
  7. Extract<type_id(__VA_ARGS__) >::type_t

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

[cpp] view plaincopy

  1. /*
  2. Encode a expression
  3. */
  4. template <typename T>
  5. struct Encode { typedef T* type_t; };
  6. template <typename T>
  7. typename Encode<T>::type_t encode(const T&);
  8. template <typename T>
  9. typename Encode<T>::type_t encode(T&);

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

最后把类型解出来:

[cpp] view plaincopy

  1. /*
  2. Decode a type
  3. */
  4. template <typename T>
  5. struct Decode;
  6. template <typename T>
  7. struct Decode<T*> { typedef T type_t; };
  8. #define type_of(...) \
  9. Decode<type_extract(encode((__VA_ARGS__)))>::type_t

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

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

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

[cpp] view plaincopy

  1. template<typename T>
  2. struct Extract<TypeID<typeid(T*)> >
  3. {
  4. typedef T* type_t;
  5. };

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

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

[cpp] view plaincopy

  1. #define REGISTER_TYPE(type) \
  2. template<> \
  3. struct Extract<type_id(type*) > { typedef type* type_t; };
  4. REGISTER_TYPE(int)
  5. REGISTER_TYPE(long)
  6. REGISTER_TYPE(char)
  7. REGISTER_TYPE(double)

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

[cpp] view plaincopy

  1. class A
  2. {
  3. public:
  4. virtual void func() { std::cout << "A" << std::endl; }
  5. };
  6. REGISTER_TYPE(A)
  7. class B : public A
  8. {
  9. public:
  10. virtual void func() { std::cout << "B" << std::endl; }
  11. };
  12. REGISTER_TYPE(B)
  13. int main()
  14. {
  15. B bb;
  16. A& aa = bb;
  17. type_of(aa) cc;
  18. cc.func();
  19. return 0;
  20. }

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


三、全自动的typeof

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

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

[cpp] view plaincopy

  1. /*
  2. Extract a type from TypeID
  3. */
  4. struct empty_t {};
  5. template<typename ID, typename T = empty_t>
  6. struct Extract;
  7. template<typename ID>
  8. struct Extract<ID, empty_t>
  9. {
  10. template <bool>
  11. struct id2type;
  12. };
  13. template<typename ID, typename T>
  14. struct Extract : Extract<ID, empty_t>
  15. {
  16. template <>
  17. struct id2type<true> { typedef T type_t; };
  18. };
  19. #define type_extract(...) \
  20. Extract<type_id(__VA_ARGS__) >::id2type<true>::type_t

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

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

[cpp] view plaincopy

  1. /*
  2. Register a type
  3. */
  4. template<typename T, typename ID>
  5. struct Register : Extract<ID, T>
  6. {
  7. typedef typename id2type<true>::type_t type_t;
  8. };

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

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

[cpp] view plaincopy

  1. template <typename T>
  2. struct Encode
  3. {
  4. typedef T* enc_type_t;
  5. typedef Register<enc_type_t, type_id(enc_type_t)> reg_type;
  6. typedef typename reg_type::type_t type_t;
  7. };

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

扫尾工作:

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

[cpp] view plaincopy

  1. #ifdef __GNUC__
  2. #define type_of(...) __typeof((__VA_ARGS__))
  3. #else
  4. #define type_of(...) \
  5. Decode<type_extract(encode((__VA_ARGS__)))>::type_t
  6. #endif
  7. #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

时间: 2024-08-06 17:49:39

C++ 下 typeof 的实现的相关文章

ajax系列之用jQuery的ajax方法向服务器发出get和post请求

打算写个ajax系列的博文,主要是写给自己看,学习下ajax的相关知识和用法,以更好的在工作中使用ajax. 假设有个网站A,它有一个简单的输入用户名的页面,界面上有两个输入框,第一个输入框包含在一个form表单里用来实现form提交,第二个输入框是单独的.没有包含在form里,下面就用这两个输入框来学习下jQuery的ajax. 1,前端的html和javascript代码 页面html 1 <main style="text-align: center; margin: 200px a

jquery源码解析:jQuery工具方法详解1

jQuery的工具方法,其实就是静态方法,源码里面就是通过extend方法,把这些工具方法添加给jQuery构造函数的. jQuery.extend({       //当只有一个对象时,就把这个对象中的属性和方法扩展到this对象中,这里的this指向jQuery expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), //唯一性,core_version 为jQuery

一、基本的格式化

1. 缩进 使用统一的缩进风格非常重要,适当的缩进,会提高代码的可读性. (1)使用制表符进行缩进 每一个缩进层级都使用单独的制表符(tab character)表示,一个缩进层级是一个制表符,两个缩进层级是两个制表符.好处在于,制表符和缩进层级之间是一对一的关系,而且大部分的文本编辑器都可以配置制表符的展现长度.缺点在于不同系统和不同编辑器对于制表符的解释不一样. (2)使用空格进行缩进 三种具体做法:2个空格.4个空格.8个空格来表示一个缩进.4个空格来标识一个缩进是一种比较折中的选择,使用

一个jQuery扩展工具包

带有详尽注释的源代码: var jQuery = jQuery || {}; // TODO // ###################################string操作相关函数################################### jQuery.string = jQuery.string || {}; /** * 对目标字符串进行html解码 * * @name jQuery.string.decodeHTML * @function * @grammar j

Javascript小括号“()”的多义性

摘要:本文主要介绍JavaScript中小括号有五种语义. Javascript中小括号有五种语义 语义1,函数声明时参数表 function func(arg1,arg2){ // ... } 语义2,和一些语句联合使用以达到某些限定作用 // 和for in一起使用 for(var a in obj){ // ... } // 和if一起使用 if(boo){ //... } // 和while一起使用 while(boo){ // ... } // 和do while一起使用 do{ //

ECMAScript 总结

<script>元素 若果有src属性,元素内不可再包含JavaScript代码.如果包含了嵌入代码,则只会下载并执行外部脚本文件,嵌入的代码会被忽略. src属性可以指向其所在页面之外的其他域的完整URL.不过在访问自己不能控制的服务器上的JavaScript代码要多加小心. 一般将JavaScript引用放在<body>元素的结束标签之前.因为如果将其放在<head>元素中,这就意味着必须等到所有的文件下载.解析和执行以后才能呈现页面内容,如果,外部文件有很多代码,

JavaScript进阶之路(二)——变量与基本数据类型

前言 JavaScript中的变量为松散类型,所谓松散类型就是指当一个变量被申明出来就可以保存任意类型的值,就是不像SQL一样申明某个键值为int就只能保存整型数值,申明varchar只能保存字符串.一个变量所保存值的类型也可以改变,这在JavaScript中是完全有效的,只是不推荐.相比较于将变量理解为“盒子“,<JavaScript编程精解>中提到应该将变量理解为“触手”,它不保存值,而是抓取值.这一点在当变量保存引用类型值时更加明显. JavaScript中变量可能包含两种不同的数据类型

[转帖] javascript中括号的几种含义

小括号       JavaScript中小括号有五种语义       语义1,函数声明时参数表              function func(arg1,arg2){                // ...              }         语义2,和一些语句联合使用以达到某些限定作用              // 和for in一起使用                      for(var a in obj){                // ...    

jQv1.5源代码重读

/*! * jQuery JavaScript Library v1.5 * http://jquery.com/ * * Copyright 2011, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * Includes Sizzle.js * http://sizzlejs.com/ * Copyright 2011, The Dojo Fou