Explode Tuple in C++11

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 Expander<K - 1, RET, F, Tup>::expand (
        forward<F>(func),
        forward<Tup>(t),
        get<K - 1>(forward<Tup>(t)),
        forward<Ts>(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<Ts>(args)...);
  }
};

其中forward<T>... 将对每个参数都做 forward.

有了 Expander 的定义, 我们已经能通过如下代码实现传参了:

void f(int, string) { /* ...*/ }
auto t = make_tuple(40, "a");
Expander<2, voiddecltype(f), decltype(t)&>::expand(f, t);

当然, 我们还想让世界更简单一点, 所以再写一个模板函数:

template <class F, class... Ts>
auto explode(F&& func, tuple<Ts...>& t)
  -> typename result_of<F(Ts...)>::type {
  return Expander<
         sizeof...(Ts),
         typename result_of<F(Ts...)>::type,
         F,
         tuple<Ts...>&
       >::expand(func, t);
}

其中用sizeof... 运算符取出 tuple 模板参数个数. 用std::result_of 获得 func 的返回值类型.

ps: C++11 的result_of 实现方式如下:

template<class F, class... ArgTypes>
struct result_of<F(ArgTypes...)> {
    typedef decltype(
                    std::declval<F>()(std::declval<ArgTypes>()...)
                    ) 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<Ts...>& t)
  -> typename result_of<F(Ts...)>::type {
  return Expander<
         sizeof...(Ts),
         typename result_of<F(Ts...)>::type,
         F,
         const tuple<Ts...>&
       >::expand(func, t);
}

template <class F, class... Ts>
auto explode(F&& func, tuple<Ts...>&& t)
  -> typename result_of<F(Ts...)>::type {
  return Expander<
         sizeof...(Ts),
         typename result_of<F(Ts...)>::type,
         F,
         tuple<Ts...>&&
       >::expand(func, move(t));
}

不得不感慨..C++11 出来之后, 那些能折腾模板的人折腾完 boost 又有事干了..

Comments