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表达式的动态类型推导了。
二、自动分配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; }
- };
- REGISTER_TYPE(A)
- class B : public A
- {
- public:
- virtual void func() { std::cout << "B" << std::endl; }
- };
- 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://blog.csdn.net/markl22222/article/details/10474591
C++ 下 typeof 的实现的更多相关文章
- 一步步学习javascript基础篇(4):面向对象设计之创建对象(工厂、原型和构造函数等模式)
前面我们介绍了可以通过Object构造函数或对象字面量都可以用来创建单个对象,但是如果需要创建多个对象的话,显然很多冗余代码. 接下来介绍几种模式来创建对象.不过在此之前,我们还是先来了解下 type ...
- jQ1.5源码注释以及解读RE
jQ作为javascript的库( ▼-▼ ), 尽善尽美, 代码优美, 值得学习. 这一周平常上班没啥事也看jQ1.5的代码, 今天周六差不多看完了(Sizzle部分还没看), 重新看了一下, ...
- Javascript小括号“()”的多义性
摘要:本文主要介绍JavaScript中小括号有五种语义. Javascript中小括号有五种语义 语义1,函数声明时参数表 function func(arg1,arg2){ // ... } 语义 ...
- C/C++语言的一些精简归纳
前言:本想直接写个关于OC语言,但觉得还是要说下C先. 先语言特性 C是面向过程的,没有类和对象概念,也就没有什么封装(这个?).继承.多态等特性. 而且是是中级语言,其编译过程包括:预编译(incl ...
- JavaScript原型链和instanceof运算符的暧昧关系
时间回到两个月前,简单地理了理原型链.prototype以及__proto__之间的乱七八糟的关系,同时也简单了解了下typeof和instanceof两个运算符,但是,anyway,试试以下两题: ...
- Javascript 中的小括号 “()” 的多义性
Javascript 中小括号有5 种语义 语义1:函数声明时参数表 1 function func(arg1, arg2){ 2 // ... 3 } 语义2:和一些语句联合使用以 ...
- 一个jQuery扩展工具包
带有详尽注释的源代码: var jQuery = jQuery || {}; // TODO // ###################################string操作相关函数### ...
- 《javascript权威指南》读书笔记 -part2
我真的很佩服副院长~他是一个很有耐心 极其细致的人 工作态度严谨 代码简洁风格统一~再乱遭遭的代码只要经过他的手就会变的很漂亮 羡煞我也~ 不说废话了 还是乖乖看书吧~maybe可能也许的某一天 ...
- JavaScript编程风格--基本的格式化
缩进层级 推荐4个空格字符作为一个缩进层级. 语句结尾 推荐不要省略分号. 行的长度 最好一行不超过80个字符. 换行 在运算符后换行,下一行增加两个层级的缩进. ...
随机推荐
- golang sha1 signature
package models import ( "crypto/hmac" "crypto/sha1" "encoding/base64" ...
- 前端开发面试题收集 JS
前端开发面试题收集-JS篇 收集经典的前端开发面试题 setTimeout的时间定义为0有什么用? javascript引擎是单线程处理任务的,它把任务放在队列中,不会同步执行,必须在完成一个任务后才 ...
- Python爬虫实战(2):爬取京东商品列表
1,引言 在上一篇<Python爬虫实战:爬取Drupal论坛帖子列表>,爬取了一个用Drupal做的论坛,是静态页面,抓取比较容易,即使直接解析html源文件都可以抓取到需要的内容.相反 ...
- ASP.NET 网站在域环境内配置授权访问
在 IIS 中,通常需要配置域中的某些用户或者用户组有权限访问部署的 WEB 站点或者 WEB 服务.我们知道要实现这种功能可以有如下几种方式: 代码,获取当前用户,到域服务器上去验证当前用户是否为合 ...
- 文本导出到pdf文件
程序中数据导出是经常有的需求,今天学习把文本导出到pdf文件.主要是用QPrinter,QPainter TextEditToPdf::TextEditToPdf(QWidget *parent, Q ...
- 普林斯顿大学算法课 Algorithm Part I Week 3 比较器 Comparators
比较器接口(Comparator interface):用可选顺序(alternate order)进行排序 public interface Comparator<key> int co ...
- 量身定制顺美男女西服、衬衫、大衣、T恤等 - 北京58同城
量身定制顺美男女西服.衬衫.大衣.T恤等 - 北京58同城 量身定制顺美男女西服.衬衫.大衣.T恤等 发布时间:2014-04-11浏览2次
- Spring、Bean 的作用域
Singleton作用域(默认) 当一个bean的作用域为singleton,那么Spring Ioc容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则 ...
- 用Meta 取消流量器缓存实现每次访问都刷新页面方便调试
如果想禁止浏览器从本地缓存中调阅页面,可以设置网页不保存在缓存中,每次访问都刷新页面,下面是Meta在这方便的用法,需要的朋友可以参考下: <!-- 禁止浏览器从本地缓存中调阅页面.--> ...
- Sublime-text 自己定义快捷键攻略
好吧.我承认今天非常悲剧,本来上午就写好了这篇博文,公布之后,在自己的博文里怎么也找不到. 所以如今又又一次写了一份.希望大家能顶一下吧...Nothing is better than your s ...