編譯器對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加價一倍】