std::tuple
是C++11中的一个好东西. 它功能上算是std::pair
的扩展,
但有一些其他的用法.
例如, 可以借用tuple来模拟多值返回(python中也是这样), SugarCpp 中就是使用tuple实现了简洁的多值返回语法:
tuple<T, T> sort<T>(a: T, b: T)
return a < b ? (a, b) : (b, a)
tuple也可以用于模拟python中的Parallel Assignment:
int i; double d;
tie(i, d) = make_tuple(10, 2.2);
对于接受tuple做参数的函数, 想要原地创建一个tuple传进去应该怎么办呢?
使用std::make_tuple
显然会增加一次复制开销.
这时候可以使用forward_as_tuple
:
void f(tuple<int, string> x) { /* ...*/ }
f(make_tuple(1, "hah")); // Copy!
f(forward_as_tuple(1, "hah")); // Perfect
// f({1, "hah"}); // ERROR, cannot use initialization list
但是反过来呢? 对于一个接受一列参数的函数, 怎样将一个已存在的tuple传进去? 我们肯定期待python的argument unpacking写法:
def func(a, b, c):
print a, b, c # 1 2 3
tup = (2, 3)
func(1, *tup)
而不希望看到这样:
void f(int, string) { /* ...*/ }
auto t = make_tuple(40, "a");
f(get<0>(t), get<1>(t));
C++中显然没法像python那么elegant, 但是我发现有人用模板实现了类似的事情, 称为Tuple Explode.
它大概长成这个样子:
template <unsigned K, class RET, class F, class Tup>
struct Expander {
template <class... Ts>
static RET expand(F&& func, Tup&& t, Ts && ... args) {
return Expander1, RET, F, Tup>::expand (
forward(func),
forward(t),
get 1>(forward(t)),
forward(args)...
);}
};
K是一个counter, 用来数当前扩展到第几个参数, 调用时赋为参数个数. RET是函数func的返回值类型,
F是函数类型.
看看expand
函数做了什么: 首先, 传入的func, t, ...args都是universal reference, 因此递归时用std::forward
保持其型别不变.
同时, 从tuple中取出第K个元素, 插入到参数列表args...
的首部, 随后K减1.
因此递归结束时的specialization里, 我们应该调用我们要的函数了:
template <class RET, class F, class Tup>
struct Expander<0, RET, F, Tup> {
template <class... Ts>
static RET expand(F&& func, Tup&&, Ts... args) {
return func(forward(args)...);
}
};
其中forward
将对每个参数都做forward.
有了Expander的定义, 我们已经能通过如下代码实现传参了:
void f(int, string) { /* ...*/ }
auto t = make_tuple(40, "a");
Expander<2, void, decltype(f), decltype(t)&>::expand(f, t);
当然, 我们还想让世界更简单一点, 所以再写一个模板函数:
template <class F, class... Ts>
auto explode(F&& func, tuple& t)
-> typename result_of::type {
return Expander<
sizeof...(Ts),
typename result_of ::type,
F,
tuple &
>::expand(func, t);
}
其中用sizeof...
运算符取出tuple模板参数个数.
用std::result_of
获得func的返回值类型.
ps: C++11的result_of
实现方式如下:
template<class F, class... ArgTypes>
struct result_of {
typedef decltype(
std::declval ()(std::declval()...)
) type;
};
有了这样定义的explode
函数之后, 就可以方便的实现tuple传参了..:
int f(int, string) { /* ...*/ }
auto t = make_tuple(40, "a");
int x = explode(f, t);
另外, 我们的explode
支持的类型还不够完整..
加上const reference和 rvalue reference, 以便在其他地方使用:
template <class F, class... Ts>
auto explode(F&& func, const tuple& t)
-> typename result_of::type {
return Expander<
sizeof...(Ts),
typename result_of ::type,
F,
const tuple &
>::expand(func, t);
}
template <class F, class... Ts>
auto explode(F&& func, tuple && t)
-> typename result_of::type {
return Expander<
sizeof...(Ts),
typename result_of ::type,
F,
tuple &&
>::expand(func, move(t));
}
不得不感慨..C++11出来之后, 那些能折腾模板的人折腾完boost又有事干了..