可變引數模板

可變引數模板是模板程式設計時，模板引數的個數可變的情形。已經支援可變引數模板的程式語言有D語言與C++。C++11.宣告.C++11之前，模板在宣告時必須有 固定數量的模板引數。C++11允許模板定義有任意型別任意數量的模板引數。例如，STL的類模板tuple可以有任意個數的型別名：template class tuple;如例項化為具有3個型別實參：tuple some_instance_name;也可以有0個實參，如 codice_1也是可以的。如果不希望可變引數模板有0個模板實參，可以如下宣告：template class tuple;可變引數模板也適用於函式模板，這不僅給可變引數函式，還允許類似printf的函式處理不平凡物件。例如：template void printf(const std::string &amp;str_format, Params... parameters);用途.省略號(...)在可變引數模板中有兩種用途：具體例子見下文。實際上，能夠接受可變引數個數的引數包展開的場合，必須是能接受任意個數的逗號分隔開的表示式列表，這也就是上述幾種場合。如果在一個引數包展開中同時出現了兩個引數包的名字，則二者在一起同時展開，且應該具有相同長度。這可能出現在類模板帶有一個引數包，它的巢狀的類模板或成員函式模板帶有另一個引數包。使用方法.可變引數模板可遞迴使用。可變模板引數自身並不可直接用於函式或類的實現。例如，printf的C++11可變引數的替換版本實現：void printf(const char *s) //已經沒有額外的引數了，這裡將要耗盡字串swhile (*s) {if (*s == '%') {if (*(s + 1) == '%') {++s;else {throw std::runtime_error("invalid format string: missing arguments");std::cout « *s++;templatevoid printf(const char *s, T value, Args... args) //處理一對： while (*s) {if (*s == '%') {if (*(s + 1) == '%') {++s;else {std::cout « value;printf(s + 1, args...); // call even when *s == 0 to detect extra argumentsreturn;std::cout « *s++;throw std::logic_error("extra arguments provided to printf");這是一個遞迴實現的模板函式。注意這個可變引數模板實現的printf呼叫自身或者在args...為空時呼叫基本實現版本。沒有簡單機制去在可變模板引數的每個單獨值上迭代。幾乎沒有什麼方式可以把引數包轉為單獨實參來使用。通常這靠函式過載，或者當函式可以每次撿出一個實參時用啞擴充套件標記：templatetype print(type param)std::cout«param«' ';return param;template inline void expand(Args&amp;&amp;... args) {pass( print(args)... );int main()expand(42, "answer", true);上例中的"pass"函式是必須的，因為引數包用逗號展開後只能作為被逗號分隔開的一組函式呼叫實參，而不是作為逗號運算子，從而"pass"函式所能接受的呼叫實參個數必須是可變的，也即"pass"函式必須是可變引數函式。codice_2編譯不能透過。 此外，上述辦法要求codice_3的返回型別不能是void；且所有對print的呼叫在一個非確定的順序，因為函式實參求值的順序是不確定的。如果要避免這種不確定的順序，可以用大括號封閉的，這保證了嚴格的從左到右的求值順序。為避免void返回型別帶來的麻煩，使用逗號運算子使得每個擴充套件元素總是返回1。例如：template void some_function(T value)std::cout«value«' ';template inline void expand(Args&amp;&amp;... args) {const int size = sizeof...(args) + 2;int arr[size]{1，(some_function(args),1 )...，2};std::cout«std::endl«sizeof(arr)/sizeof(int); //也可以用sizeof...(Args)運算子int main()expand(42, "answer", true);另一種方法使用過載函式的遞迴的終結版函式。這更為通用，但要求更多努力寫更多程式碼。一個函式要求某種型別的實參與引數包。另一個函式沒有引數。如下例：int func() {} // termination versiontemplateint func(const Arg1&amp; arg1, const Args&amp;... args)process( arg1 );func(args...); // note: arg1 does not appear here!如果args...包含至少一個實參，則將呼叫第二個版本的函式；如果引數包為空將呼叫第一個“終結”版的函式。可變引數模板可用於異常規範。例如：template  class ClassName : public BaseClasses... {public:這個例子中的解包運算元將複製所有模板引數型別為codice_4的基型別。建構函式取每個基類的引用，並初始化每個基類。對於函式模板，可變模板引數可以轉發：template struct SharedPtrAllocator {template construct_with_shared_ptr(Args&amp;&amp;... params) {return std::shared_ptr(params)...));};上例中，實參列表被解包給TypeToConstruct的建構函式。codice_5的句法是以適當的型別轉發實參。解包運算元將把轉發語法應用於每個引數。模板引數包中實參的個數可以如下確定：template struct SomeStruct {static const int size = sizeof...(Args);例如codice_6為2，codice_7為0。需要注意，codice_8與codice_9是兩個不同的運算子。Lambda捕獲例子：templatevoid f(Args... args) {auto lm = [&amp;, args...] { return g(args...); };lm();編譯器實現.GCC尚不支援lambda表示式包含為展開的引數包，因此下述語句編譯不透過：int arr[]{([&amp;]{ std::cout « args « std::endl; }(), 1)...};Visual C++ 2013支援上述風格的語句。當然，這裡的lambda函式不是必需的，通常的表示式即可：int arr[]{(std::cout « args « std::endl, 1)...};例子.下述程式碼實現了C++14引入的make_integer_sequence函式模板。它產生一個模板類，其模板引數為0,1,2...,N。可用於生成或訪問std::tuple// using aliases for cleaner syntaxtemplate struct seq { using type = seq; };template struct concat;templatestruct concat, seq&lt;I2...»: seq {};templatestruct make_integer_sequence : concat::type {};template {};int printItem(unsigned k)std::cout « k « ' ';return 0;templatevoid printTemplate(seq a)int nn[] = { printItem(I1)... };int main()make_integer_sequence a;printTemplate(a);輸出為0 1 2 3 4 5 6 7 8 9更多文章關於可變引數結構而非模板：