IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Explode Tuple in C++11

    Yuxin Wu (ppwwyyxxc@gmail.com)发表于 2014-12-11 15:39:41
    love 0

    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),
                    get1>(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又有事干了..



沪ICP备19023445号-2号
友情链接