# 可變參數模板(Variadic Templates)
要解決的問題:
* 怎么創建一個擁有1個、2個或者更多的初始化器的類?
* 怎么避免創建一個實例而只拷貝部分的結果?
* 怎么創建一個元組?
最后的問題是關鍵所在:考慮一下元組!如果你能創建并且訪問一般的元組,那么剩下的問題也將迎刃而解。
這里有一個例子(摘自“可變參數模板簡述(A brief introduction to Variadic templates)”(參見參考)),要構建一個廣義的、類型安全的printf()。這個方法比用boost::format好的多,但是考慮一下:
```
const string pi = “pi”;
const char* m =
“The value of %s is about %g (unless you live in %s).n”;
printf(m, pi, 3.14159, “Indiana”);
```
這是除了格式字符串之外,沒有其它參數的情況下調用printf()的一個最簡單的例子了,所以我們將要首先解決:
```
void printf(const char* s)
{
while (s && *s) {
if (*s==’%’ && *++s!=’%') //保證沒有更多的參數了
//%%(轉義字符,在格式字符串中代表%
throw runtime_error(“格式非法: 缺少參數”);
std::cout << *s++<<endl;
}
}
```
這個處理好之后,我們必須處理有更多參數的printf():
```
template<typename T, typename... Args> // 注意這里的"..."
void printf(const char* s, T value, Args... args) // 注意"..."
{
while (s && *s) {
//一個格式標記(避免格式控制符)
if (*s=='%' && *++s!='%') {
std::cout << value;
return printf(++s, args...);//使用第一個非格式參數
}
std::cout << *s++;
}
throw std::runtime error("extra args provided to printf");
}
```
這段代碼簡單地“去除”了開頭的無格式參數,之后遞歸地調用自己。當沒有更多的無格式參數的時候,它調用第一個(很簡單)printf()(如上所示)。這也是標準的函數式編程在編譯的時候做的(?)。注意,`<<`的重載代替了在格式控制符當中(可能會有錯誤)的花哨的技巧。(譯注:我想這里可能指的是使用重載的`<<`輸出操作符,就可以避免使用各種技巧復雜的格式控制字符串。) Args…定義的是一個叫做“參數包”的東西。這個“參數包”僅僅是一個(有各種類型的值的)隊列,而且這個隊列中的參數可以從頭開始進行剝離(處理)。如果我們使用一個參數調用printf(),函數的第一個定義(printf(const char*))就被調用。如果我們使用兩個或者更多的參數調用printf(),那么函數的第二個定義(printf(const char*, T value, Args… args))就會被調用,把第一個參數當作字符串,第二個參數當作值,而剩余的參數都打包到參數包args中,用做函數內部的使用。在下面的調用中:
```
printf(++s, args…);
```
參數包args被打開,所以參數包中的下一個參數被選擇作為值。這個過程會持續進行,直到args為空(所以第一個printf()最終會被調用)。
如果你對函數式編程很熟悉的話,你可能會發現這個語法和標準技術有一點不一樣。如果發現了,這里有一些小的技術示例可能會幫助你理解。首先我們可以聲明一個普通的可變參數函數模板(就像上面的printf()):
```
template<class ... Types>
// 可變參數模板函數
//(補充:一個函數可以接受若干個類型的若干個參數)
void f(Types ... args);
f(); // OK: args不包含任何參數
f(1); // OK: args有一個參數: int
f(2, 1.0); // OK: args有兩個參數: int和double
```
我們可以建立一個具有可變參數的元組類型:
```
template<typename Head, typename... Tail>
//這里是一個遞歸
//一個元組最基本要存儲它的head(第一個(類型/值))對
//并且派生自它的tail(剩余的(類型/值))對
//注意,這里的類型被編碼,而不是按一個數據來存儲
class tuple<Head, Tail...>
: private tuple<Tail...> {
typedef tuple<Tail...> inherited;
public:
tuple() { } // 默認的空tuple
//從分離的參數中創建元組
tuple(typename add_const_reference<Head>::type v,
typename add_const_reference<Tail>::type... vtail)
: m_head(v), inherited(vtail...) { }
// 從另外一個tuple創建tuple:
template<typename... VValues>
tuple(const tuple<VValues...>& other)
: m_head(other.head()), inherited(other.tail()) { }
template<typename... VValues>
tuple& operator=(const tuple<VValues...>& other) // 等于操作
{
m_head = other.head();
tail() = other.tail();
return *this;
}
typename add_reference<Head>::type head()
{ return m_head; }
typename add_reference<const Head>::type head() const
{ return m_head; }
inherited& tail() { return *this; }
const inherited& tail() const { return *this; }
protected:
Head m_head;
}
```
有了定義之后,我們可以創建元組(并且復制和操作它們):
```
tuple<string,vector,double> tt("hello",{1,2,3,4},1.2);
string h = tt.head(); // "hello"
tuple<vector<int>,double> t2 = tt.tail();
```
要實現所有的數據類型可能會比較乏味,所以我們經常減少參數的類型,例如,可以使用標準庫中的make_tuple()函數:
```
template<class... Types>
// 這個定義十分簡單(參見標準20.5.2.2)
tuple<Types...> make_tuple(Types&&... t)
{
return tuple<Types...>(t...);
}
string s = "Hello";
vector<int> v = {1,22,3,4,5};
auto x = make_tuple(s,v,1.2);
```
參考:
* Standard 14.6.3 Variadic templates
* [N2151==07-0011] D. Gregor, J. Jarvi:
[Variadic Templates for the C++0x Standard Library](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2151.pdf).
* [N2080==06-0150] D. Gregor, J. Jarvi, G. Powell:
[Variadic Templates (Revision 3)](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2080.pdf).
* [N2087==06-0157] Douglas Gregor:
[A Brief Introduction to Variadic Templates](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2087.pdf).
* [N2772==08-0282] L. Joly, R. Klarer:
[Variadic functions: Variadic templates or initializer lists? — Revision 1](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2772.pdf).
* [N2551==08-0061] Sylvain Pion:
[A variadic std::min(T, …) for the C++ Standard Library (Revision 2)](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2551.pdf) .
* Anthony Williams:
[An introduction to Variadic Templates in C++0x](http://www.devx.com/cplus/Article/41533).
DevX.com, May 2009.
- C++11 FAQ中文版 - C++11 FAQ
- Stroustrup先生關于中文版的授權許可郵件
- Stroustrup先生關于C++11 FAQ的一些說明
- 關于C++11的一般性的問題
- 您是如何看待C++11的?
- 什么時候C++0x會成為一部正式的標準呢?
- 編譯器何時將會實現C++11標準呢?
- 我們何時可以用到新的標準庫文件?
- C++0x將提供何種新的語言特性呢?
- C++11會提供哪些新的標準庫文件呢?
- C++0x努力要達到的目標有哪些?
- 指導標準委員會的具體設計目標是什么?
- 在哪里可以找到標準委員會的報告?
- 從哪里可以獲得有關C++11的學術性和技術性的參考資料?
- 還有哪些地方我可以讀到關于 C++0x的資料?
- 有關于C++11的視頻嗎?
- C++0x難學嗎?
- 標準委員會是如何運行的?
- 誰在標準委員會里?
- 實現者應以什么順序提供C++11特性?
- 將會是C++1x嗎?
- 標準中的"concepts"怎么了?
- 有你不喜歡的C++特性嗎?
- 關于獨立的語言特性的問題
- __cplusplus宏
- alignment(對齊方式)
- 屬性(Attributes)
- atomic_operations
- auto – 從初始化中推斷數據類型
- C99功能特性
- 枚舉類——具有類域和強類型的枚舉
- carries_dependency
- 復制和重新拋出異常
- 常量表達式(constexpr)
- decltype – 推斷表達式的數據類型
- 控制默認函數——默認或者禁用
- 控制默認函數——移動(move)或者復制(copy)
- 委托構造函數(Delegating constructors)
- 并發性動態初始化和析構
- noexcept – 阻止異常的傳播與擴散
- 顯式轉換操作符
- 擴展整型
- 外部模板聲明
- 序列for循環語句
- 返回值類型后置語法
- 類成員的內部初始化
- 繼承的構造函數
- 初始化列表
- 內聯命名空間
- Lambda表達式
- 用作模板參數的局部類型
- long long(長長整數類型)
- 內存模型
- 預防窄轉換
- nullptr——空指針標識
- 對重載(override)的控制: override
- 對重載(override)的控制:final
- POD
- 原生字符串標識
- 右角括號
- 右值引用
- Simple SFINAE rule
- 靜態(編譯期)斷言 — static_assert
- 模板別名(正式的名稱為"template typedef")
- 線程本地化存儲 (thread_local)
- unicode字符
- 統一初始化的語法和語義
- (廣義的)聯合體
- 用戶定義數據標識(User-defined literals)
- 可變參數模板(Variadic Templates)
- 關于標準庫的問題
- abandoning_a_process
- 算法方面的改進
- array
- async()
- atomic_operations
- 條件變量(Condition variables)
- 標準庫中容器方面的改進
- std::function 和 std::bind
- std::forward_list
- std::future和std::promise
- 垃圾回收(應用程序二進制接口)
- 無序容器(unordered containers)
- 鎖(locks)
- metaprogramming(元編程)and type traits
- 互斥
- 隨機數的產生
- 正則表達式(regular expressions)
- 具有作用域的內存分配器
- 共享資源的智能指針——shared_ptr
- smart pointers
- 線程(thread)
- 時間工具程序
- 標準庫中的元組(std::tuple)
- unique_ptr
- weak_ptr
- system error