現在我們有這樣一坨代碼:
- 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