C++14尝鲜:用泛型lambda实现tuple

Is there a name for this tuple-creation idiom?
Fun with Lambdas: C++14 Style (part 3)
#include <iostream>

auto List = [](auto ...xs) { 
    return [=](auto access) { return access(xs...); }; 
}; 
 
auto length = [](auto xs) { 
    return xs([](auto ...z) { return sizeof...(z); }); 
};

auto fmap = [](auto func) {
    return [func] (auto alist) {
        return alist([func](auto... xs) { return List(func(xs)...); });
    };
};

int main()
{
    std::cout << length(List(1, '2', "3")); // 3
    auto twice = [](auto i) { return 2*i; };
    auto print = [](auto i) { std::cout << i << " "; return i; };
    auto l1 = List(1, 2, 3, 4);
    auto l2 = fmap(twice)(l1);
    auto l3 = fmap(print)(l2); // 8 6 4 2
}
代码说明
  • 这里List是一个泛型lambda,它接收一个变长的参数包xs,并返回一个闭包[=](auto access) { return access(xs...); }。通过值捕获,这个lambda在它所返回的闭包中存放了参数包xs中所有值的拷贝。也可以说,这个闭包中存放了参数包xs的拷贝。比如List(1, '2', "3")就是一个存放了1, '2', "3"这三个值的闭包,而List(1, 2, 3, 4)则是一个存放了四个整数的闭包。
  • 现在我们把参数包xs看成一个tuple(元组)。(为什么可以把变长参数包看成元组?这是因为变长参数包与元组有很多共性:和元组一样,每一个具体的参数包的长度实际上是固定的,各个参数的类型可以不同,参数包的类型由各个参数的类型和顺序决定。)这样List(xs)这个闭包就可以看成是一个存放了tuple的闭包。也可以说这个闭包本身就可以被看成一个tuple。
  • 如果我们把List(xs)这个闭包看成一个tuple,那么这个闭包就成了一个类似tuple的数据结构,而List也就成了创建这种数据结构的构造器。比如List(1, '2', "3")就成了一个类似tuple<int, char, const char*>(1, '2', "3")的数据结构,其作用相当于make_tuple(1, '2', "3")。可以看出这是一种借助于“将lambda表达式展开成函数对象”这种编译器的魔法来创建tuple的新的惯用法。
  • 除了可以被看成一个tuple,List(xs)本身还是一个闭包。这个闭包接收一个名为access的函数(也可以是其他可调用的实体,比如lambda表达式和函数对象)参数,在函数体内将自身所存放的参数包作为参数传递给access函数,然后返回access函数的返回值。
  • 下面看看List(xs)这种新的tuple有什么具体应用。
    length函数返回List所带参数包xs的长度。
    fmap函数使用List所带参数包xs的各个参数值调用某个函数,并将所有的返回值重新组装成List。
  • length(List(1, '2', "3"))
    = List(1, '2', "3")([](auto ...z) { return sizeof...(z); })
    = [](auto ...z) { return sizeof...(z); }(1, '2', "3")
    = sizeof...(pack(1, '2', "3"))
    = 3
  • l2 = fmap(twice)(l1)
    = l1([](auto... xs) { return List(twice(xs)...); })
    = List(1, 2, 3, 4)([](auto... xs) { return List(twice(xs)...); })
    = [](auto... xs) { return List(twice(xs)...); }(1, 2, 3, 4)
    = List(twice(pack(1, 2, 3, 4))...)
    = List(twice(1), twice(2), twice(3), twice(4))
    = List([](auto i) { return 2*i; }(1), [](auto i) { return 2*i; }(2), [](auto i) { return 2*i; }(3), [](auto i) { return 2*i; }(4))
    = List(2, 4, 6, 8)
#include <iostream>

auto List = [](auto ...xs) { 
    return [=](auto access) { return access(xs...); }; 
}; 

auto fmap = [](auto func) {
    return [func] (auto alist) {
        return alist([func](auto... xs) { return List(func(xs)...); });
    };
};

auto concat = [](auto l1, auto l2) {
    return l1([=](auto... p) {
        return l2([=](auto... q) {
            return List(p..., q...);
        });
    });
};
 
template <class Func>
auto flatten(Func)
{
    return List(); 
}
 
template <class Func, class A, class... B>
auto flatten(Func f, A a, B... b)
{
    return concat(f(a), flatten(f, b...));
}
 
auto flatmap = [](auto func) {
   return [func](auto alist) {
       return alist([func](auto... xs) { return flatten(func, xs...);  });
   };
};

int main()
{
    auto pair = [](auto i) { return List(-i, i); };
    auto print = [](auto i) { std::cout << i << " "; return i; };
    auto l1 = List(1, 2, 3);
    auto l2 = flatmap(pair)(l1);
    auto l3 = fmap(print)(l2); // 3 -3 2 -2 1 -1
}
代码说明
  • 这段代码为List(xs)这种新的tuple实现了一个新的操作flatmap。
    与fmap函数一样,flatmap函数也将使用List所带参数包xs的各个参数值调用某个函数,并将所有的返回值重新组装成List。
    但是与fmap函数有所不同的是,每次调用函数所返回的并不是单个值,而是一个List,所以实际上flatmap还需要将所有返回的List平坦化,然后再重新组装成List。
  • flatmap调用flattern函数来实际完成调用函数及平坦化任务,而flattern函数则通过递归调用自身及concat函数来完成平坦化任务。
  • 这里concat函数负责将两个List连接为一个List。
    concat(List(-1, 1), List(-2, 2))
    = List(-1, 1)([](auto... p){ return List(-2, 2)([](auto... q){List(p..., q...); }); })
    = [](auto... p){ List(-2, 2)([](auto... q){ return List(p..., q...); }); }(-1, 1)
    = List(-2, 2)([](auto... q){ return List(-1, 1, q...); })
    = [](auto... q){ return List(-1, 1, q...); }(-2, 2)
    = List(-1, 1, -2, 2)
  • l2 = flatmap(pair)(l1)
    = l1([](auto... xs){return flatten(pair, xs...);})
    = List(1, 2, 3)([](auto... xs){return flatten(pair, xs...);})
    = [](auto... xs){return flatten(pair, xs...);}(1, 2, 3)
    = flatten(pair, 1, 2, 3)
    = concat(pair(1), flattern(pair, 2, 3))
    = concat(pair(1), (concat(pair(2), flattern(pair, 3))))
    = concat(pair(1), (concat(pair(2), (concat(pair(3), flattern(pair))))))
    = concat(pair(1), (concat(pair(2), (concat(pair(3), List())))))
    = concat([](auto i){return List(-i, i);}(1), (concat([](auto i){return List(-i, i);}(2), (concat([](auto i){return List(-i, i);}(3), List())))))
    = concat(List(-1, 1), (concat(List(-2, 2), (concat(List(-3, 3), List())))))
    = concat(List(-1, 1), (concat(List(-2, 2), List(-3, 3))))
    = concat(List(-1, 1), List(-2, 2, -3, 3))
    = List(-1, 1, -2, 2, -3, 3)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值