C++11 Variadic Template
分类:技术
听说这个特性是很久以前了,总是读作「维拉迪克·坦普雷特」,一直没反应过来中文到底该叫什么,因为 C 时代的 Variadic Macro 我一直是很象形地读作「点点点」的 = =||
OK,扯远了。Variadic Template 对应中文应该是「可变参数模板」。
Parameter Pack
既然是可变参数,就需要通过某种方式来表示这些参数,而这里的解决方案就是 Parameter Pack 参数包,不知道可不可以简称「餐包」 =_,=
声明参数包的方法是在类型和名称之间加 ...
:
template<typename... Types> struct Tuple {};
Tuple<> t0; // Types 中不含参数
Tuple<int> t1; // Types 中包含一个参数:int
Tuple<int, float> t2; // Types 中包含两个参数:int 和 float
template<typename... Types> void f(Types... args);
f(); // args 中不包含参数
f(1); // args 中包含一个参数:int(1)
f(2, 1.0); // args 中包含两个参数:int(2) 和 double(1.0)
上面的两个示例中,Types
称作模板参数包,args
称作函数参数包。
参数包所包含的参数的个数可以用 sizeof...
取得。
Pack Expansion
既然提出了参数包,把所有可变参数容纳其中,那么就需要存在将其解包的操作。与 C 中 va_list
一个参数一个参数地手动解包不同,参数包的 Pack Expansion 是一口气将所有的参数以某种形式展开:
template<int... Entries>
struct IntArray {
int array[sizeof...(Entries)] = { Entries... };
};
template<typename... Types> void bar(Types... args) {}
template<typename... Types> void foo(Types... args) {
bar(&args...);
}
所谓「以某种形式」展开,就是将 pattern ...
转换为逗号分隔的 pattern_1, pattern_2, ... , pattern_N
的形式。从上面的函数 foo
就可以看出,传给 bar
的是各个参数的地址(没啥大意义);即 void foo(a, b, c)
的 &args...
会展开成 &a, &b, &c
。
std::tuple
作为一个可变参数模板的实际用例,C++11 还引入了 std::tuple
作为 std::pair
的推广形式(tuple 的意思即为元祖……哦不对,是元组……我是吃货我自重……),表示任意多个元素的组合。
使用 std::make_tuple
和 auto
可以很方便地声明一个元组:
auto x = std::make_tuple(3, 0.14, std::string("pie")); // std::tuple<int, double, std::string>
而对于各个元素的访问可以统一使用 std::get
实现(包括 std::array
和 std::pair
的大一统):
auto element = std::get<2>(x);
另一个好玩的地方是使用 std::tie
创建 lvalue reference 的 tuple:
std::set<int> some_instance_of_std_set;
std::set<int>::iterator itr;
bool success;
std::tie(itr, success) = some_instance_of_std_set.insert(2012);
虽然看着有些丑陋,但似乎可以看到些「多返回值」的影子……
当然,也可以参照 Lua 中的 _
使用 std::ignore
忽略多返回值中的特定位置的值:
int r1, r2;
std::tie(r1, std::ignore, r2) = std::make_tuple(3, 0.14, 4);
顺带的,既然是 lvalue reference,试图一句话交换两个变量的值是不可以全用 std::tie
的:
std::tie(a, b) = std::tie(b, a); // 错误方式
std::tie(a, b) = std::make_tuple(b, a); // 正确方式