# 可变参数模板(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