前些時候,我在群裡出了一道題目:将變參的類型連接配接在一起作為字元串并傳回出來,要求隻用函數實作,不能借助于結構體實作。用結構體來實作比較簡單:
測試代碼:
如果改成函數的話,試着這樣寫:
很遺憾,這樣是編譯不過的,因為編譯器不知道選擇哪個。有兩個方法可以解決這個問題,這裡主要來介紹通過逗号表達式來解決這個問題。
前段時間播放的舌尖上的中國第二季中有一集為相逢,當南北不同風味的普通食材放到一起時,立刻化腐朽為神奇變成難得的美味了,這正是食材相逢組合而成的天作之合。那麼在c++中,不同的特性相逢在一起又是什麼滋味呢?一定很奇妙。下面來看看舌尖上的c++中兩個普通食材吧。
我們知道逗号表達式會按順序執行逗号前面的表達式,比如:
d = (a = b, c);
這個表達式會按順序執行的:a先會被指派b,接着d會被指派c。
c++11的可變參數模闆增強了模闆功能,在c++11之前,類模闆和函數模闆隻能含有固定數量的模闆參數,現在c++11中的新特性可變參數模闆允許模闆定義中包含0到任意個模闆參數。可變參數模闆和普通模闆的語義是一樣的,隻是寫法上稍有差別,可變參數模闆聲明時需要在typename或class後面帶上省略号“...”。
省略号(...)的作用有兩個
聲明一個參數包,這個參數包中可以包含0到任意個模闆參數;
在模闆定義的右邊,可以将參數包展開成一個一個獨立的參數。
來看看逗号表達式和變參的相逢會産生什麼奇妙的效果。
上面的例子将分别列印出1,2,3,4,将可變參數模闆就地展開了。是的,是通過一個沒有引用的數組來就地展開的,看到這種寫法不要驚訝,這就是它們相逢産生的神奇效果,獨有的味道。
我們來看看這種奇妙的效果是如何産生的:
d = (a = b, c);
expand函數中的逗号表達式:(printarg(args),
0),也是按照這個執行順序,是先執行printarg(args),再得到逗号表達式的結果0。同時還用到了c++11的另外一個特性:初始化清單,通過初始化清單來初始化一個變長數組,
{(printarg(args), 0)...}将會展開成((printarg(arg1),0),
(printarg(arg2),0),printarg(arg3),0), etc... );最終會建立一個元素值都為0的數組int
arr[sizeof...(Args)],由于是逗号表達式,在建立數組的過程中會先執行逗号表達式前面的部分printarg(args)列印出參數,也就是說在構造int數組的過程中就将參數包展開了,這個數組的目的純粹是為了在數組構造的過程展開參數包。
再回過頭來說說本文開頭提到的那個問題,通過函數将變參類型作為字元串傳回出來。通過逗号表達式可以很輕松完成這個任務:
嘗了上面的逗号表達式和可變參數模闆的相逢産生奇妙的味道,一定還在回味之中,意猶未盡吧。我還沒說其實還有一樣食材沒說呢,如果将逗号表達式和這種食材相逢在一起便又是另外一種獨特的味道了。再來看看另外一種食材吧。
C++11新增了decltype關鍵字,用來在編譯時推導出一個表達式的類型。它的文法格式如下:
decltype(exp)
其中exp表示一個表達式(expression)。
它可以用來推導表達式标示符和表達式的類型,比如:
我們一般用decltype來推斷函數的傳回類型,和auto結合起來,組成一種傳回值類型後置的文法,比如下面的例子:
為什麼要傳回類型後置呢?因為傳回類型要依賴于模闆參數,如果将decltype(t +
u)放到函數的前面則無法編譯通過,因為在定義函數傳回值的時候,模闆參數變量都還不存在呢。是以就借助auto來将傳回類型占位住,等decltype推導之後再給auto初始化,進而擷取了函數的傳回值。
來看看逗号表達式和decltype的相逢又會産生什麼奇妙的效果。比如有這樣一個需求,我需要在編譯期判斷某個類是否存在void Reserve(int
i)函數。
這裡利用了SFINAE特性,它的全稱是Substitution failure is not an
error,比對失敗并不是錯誤,意思是用函數模闆比對規則來判斷類型的某個屬性是否存在,也就是說SFINAE可以作為一種編譯期的不完整内省方法。
這兩行代碼是關鍵,當Check比對不上時,傳回False;當比對上之後,通過逗号表達式傳回True,這樣外面就可以根據True和False來檢查是否存在該函數了。
怎麼樣這道菜的味道也相當好吧。其實還有很多c++特性的相逢産生的奇妙味道我們還沒有發現,正等待着我們去發現呢。
由于typeid可能會丢失一些類型資訊,要擷取準确的類型名稱還需要做一些專門的處理,這裡為了簡單起見,忽略了typeid擷取類型名可能不準确的影響。另外,有童孩說逗号表達式來展開變參的代碼可讀性很差,這裡我也不推薦大家在實際的代碼中也這樣寫,還是老老實實的用結構體來展開變參吧,其實通過函數來展開變參(不用逗号表達式)還有種寫法:
如果你覺得這篇文章對你有用,可以點一下推薦,謝謝。
c++11 boost技術交流群:296561497,歡迎大家來交流技術。