編譯器對C++ 11變參模板(Variadic Template)的函數包擴展實現的差別

編譯器對C++ 11變參模板(Variadic Template)的函數包擴展實現的差別 ios

題目挺繞口的。C++ 11的好東西不算太多,但變參模板(Variadic Template)確定是其中耀眼的一顆明星,在C++設計新思惟中,你能夠看到不少模版的代碼爲了支持不肯定的參數個數,而要重載1個參數到N個模板參數的N個函數。雖然種代碼通常也是用會用宏和腳步輔助生成。但我想也沒有人願意看到幾千行這種單調的函數。經過這個東東,模板的威力能夠爆發。app

目前的最新的編譯器基本都已經支持Variadic Template了,GCC 4.6和Visual studio 2013都已經支持變參模板。但今天我在寫一個Lua粘結層的時候發現了一個有意思的問題。函數

先上代碼。測試

 1 #include <iostream>
 2 
 3 
 4 template <typename ... T>
 5 void dummy_wrapper(T... t)
 6 {
 7 };
 8 
 9 template <class T>
10 T unpacker(const T t)
11 {
12     std::cout << '[' << t << ']';
13     return t;
14 }
15 
16 
17 template <typename... Args>
18 void write_line(const Args& ... data)
19 {
20     dummy_wrapper(unpacker(data)...);
21     std::cout << '\n';
22 }
23 
24 int main()
25 {
26     write_line(1, "--", "2.2.2", "--", 3.0);
27     return 0;
28 }

稍做解釋,write_line是一個接受變參的模版函數,其內部調用dummy_wrapper,這個函數幫主輔助進行包擴展,讓每一個參數都調用unpacker(data)函數。那這個代碼這個代碼的輸出預計應該是什麼?lua

我想大部分人會認爲是 [1][--][2.2.2][--][3],但其實在Visual C++ 2013和GCC 4.8上的輸出都正好相反。輸出的是[3][--][2.2.2][--][1]。是否是有點搞?spa

我以爲這個問題的緣由是由於C++的參數入棧順序是從右到左,1這個參數被計算處理後,被先扔到棧裏面。反而變成了最右的參數。因此致使了這個問題。設計

其實坦白講這種問題就是典型的規範沒有寫明的問題。其實我認爲從結果和語義正確性上講,[1][--][2.2.2][--][3]的輸出確定更加正確一些,但這個對於編譯器,就要等若干結果計算出來了,再入棧。指針

而這個問題在IBM的編譯器團隊的寫的《深刻理解C++11》上也有一節提過,IBM的編譯器XL編譯器上相似的測試輸出結果是順序的,他們也測試了GCC,他們認爲GCC輸出是混亂的(實際上是倒序的)。code

如何規避這個問題呢?有個法子是把參數反着傳遞給write_line函數,但這種方法不具有可移植性(誰知道這個bug會哪天修復,另外,你輸出東西的時候能倒着想問題?)。blog

其實能夠考慮使用遞歸的包擴展方式,

 1 #include <iostream>
 2 
 3 template <typename ... Tlist>
 4 void dummy_wrapper(Tlist... tlst)
 5 {
 6 };
 7 
 8 template <typename T, typename ... Tlist>
 9 void dummy_wrapper(T t, Tlist... tlst)
10 {
11     unpacker(t);
12     dummy_wrapper(tlst...);
13 };
14 
15 template <class T>
16 T unpacker(const T t)
17 {
18     std::cout << '[' << t << ']';
19     return t;
20 }
21 
22 template <typename... Args>
23 void write_line(const Args& ... data)
24 {
25     dummy_wrapper(data...);
26     std::cout << '\n';
27 }
28 
29 int main()
30 {
31     write_line(1, "--", "2.2.2", "--", 3.0);
32     return 0;
33 }

這樣改寫後,輸出變成[1][--][2.2.2][--][3]、

這種方式參數是一個個展開的,因此避免了上述的問題。大部分狀況能夠這樣改寫規避一部分問題。但固然這樣也不能解決全部問題,

個人代碼裏面中間有一段是這樣的,目的是爲了將一個函數註冊給lua,

 1 template<typename ret_type, 
 2     typename ...args_type>
 3 class g_functor_ret
 4 {
 5 public:
 6     static int invoke(lua_State *state)
 7     {
 8         void *upvalue_1 = lua_touserdata(state, lua_upvalueindex(1));
 9         ret_type(*fun_ptr)(args_type...) = (ret_type( *)(args_type...))(upvalue_1);
10         int para_idx = 0;
11         push_stack<ret_type>(state, fun_ptr(read_stack<args_type>(state, para_idx++)...));
12     }
13 };

fun_ptr(read_stack<args_type>(state, para_idx--)...) 這段代碼,因爲fun_ptr是一個函數指針,我不可能爲全部的函數指針都去寫一個遞歸方法,因此上面的方法無效。

怎麼辦呢?只能先把參數反向傳入,後面等編譯器有更新再慢慢改,也只有這個很挫的方法先應付。目前我能測試到的VS2013和GCC 4.8目前還都有問題。

【本文做者是雁渡寒潭,本着自由的精神,你能夠在無盈利的狀況完整轉載此文檔,轉載時請附上BLOG連接:http://www.cnblogs.com/fullsail/,不然每字一元,每圖一百不講價。對Baidu文庫和360doc加價一倍】

相關文章
相關標籤/搜索