【全网首发】lambda with template原创
你好,我是雨乐!
好久没发文了,一方面因为实在太忙了,一方面也在自我提升,发现要学的越来越多。在群里的,大抵都知道我上半年基本在重构和优化,将gcc版本进行了升级,从之前的4.9.2升级到了现在的11.2.1(支持c++17),这样在重构过程中难免要用到新特性,所以也就在这个过程中一边学习一遍应用于项目中,毕竟这样才能完全吸收为自己所用嘛。
在重构的过程中,某些使用cpp11往往需要几十行甚至上百行实现的功能,用17进行重写也就那么十几行完事,真的太方便了,今天就聊聊用过的一个比较爽的功能lambda with template,好了,开始正文吧。
从一个例子开始
现在有一个小需求,就是用lambda来实现遍历一个std::vector<>,很简单吧,我想很多人会这么写:
int main() {
auto lamb = [](std::vector<int> vec) {
for (const auto & elem : vec) {
std::cout << elem << std::endl;
}
};
std::vector<int> v = {0, 1, 2};
lamb(v);
return 0;
}
那么,如果遍历的一个double类型的vector呢?难道要再加一个支持std::vector 不成?如果我要再加自定义类型...
这个时候,可能会想到另外一个方案,即auto支持,代码如下:
int main() {
auto lamb = [](auto vec) {
for (const auto & elem : vec) {
std::cout << elem << std::endl;
}
};
std::vector<int> v = {0, 1, 2};
lamb(v);
return 0;
}
嗯,上面实现确实没问题,而且结构简单,对于各种类型的vector或者基于初始化列表的都可以支持,但是却存在着一个很大的缺陷:使用auto意味着参数可以是任何类型,甚至是一个字符串,如下:
int main() {
auto fun = [](auto vec) {
for (const auto & elem : vec) {
std::cout << elem << std::endl;
}
};
std::vector<int> v = {0, 1, 2};
fun(v);
int a = 1;
fun(a); // 这种会导致编译失败
return 0;
}
这个时候,我们可能会想到template中的一个很常用的特性SFINAE
,遂使用该特性解决上面这个问题:
template<typename T>
struct IsVector : std::false_type{};
template<typename T>
struct IsVector<std::vector<T>> : std::true_type{};
int main() {
auto fun = [](auto vec) {
if constexpr (IsVector<decltype(vec)>::value) {
for (const auto & elem : vec) {
std::cout << elem << std::endl;
}
}
};
std::vector<int> v = {0, 1, 2};
fun(v);
int a = 1;
fun(a);
return 0;
}
上述代码编译成功:
-
• IsVector是一个模板类,继承于std::false_type
-
• 传入IsVector的参数为std::vector
,继承于std::true_type -
• if constexpr表达式的意思是如果vec是一个vector则执行函数体内的for循环操作
ps: 对于con***pr这块不是很了解的话,建议看上一篇文章性能优化利器之constexpr
好了,截止到现在,上面的示例代码中lambda支持了多类型的std::vector以及传入非vector数据(比如上例中的int),那么有没有一种更简单的方式,支持多类型的std::vector呢?这就引入了lambda的新特性-template,废话不多说,直接上代码:
int main() {
auto fun1 = []<typename T>(std::vector<T> vec) {
for (const auto &elem : vec) {
std::cout << elem << std::endl;
}
};
std::vector<int> vi = {0, 1, 2};
std::vector<double> vd = {0.0, 0.1, 0.2};
fun1(vi);
fun1(vd);
return 0;
}
with std::forward
在项目中有这样一个模块,针对不同服务返回的数据,进行不同的处理,现有代码实现如下:
std::unorder_map<std::string, Handler*> handlers;
auto id = ""s; // get id from response
if (handlers.count(id)) {
handlers[id]->Process(...args);
} else {
handler->Process(...args);
}
如果想将上面这块代码使用lambda实现的话,该怎么做呢?相信看了前面的内容,看到这个问题,可以说是手到拿来了,看代码:
auto hd = xxx; //get handler
auto fun = []<tyepname T, typename Method, typename ...Args>(T &&obj, Method fun, Args ...arg) {
(std::forward<T>(obj)->*fun)(std::forward<Args>(args)...);
}
fun(hd, &Handler::Process, args...);
当然了,这块只是介绍了一种方式,可读性显然不如第一种,只是为了更好地了解lambda with template罢了。。。
今天的文章就到这,我们下期见!
你好,我是雨乐,从业十二年有余,历经过传统行业网络研发、互联网推荐引擎研发,目前在广告行业从业8年。 目前任职某互联网公司高级技术专家一职,负责广告引擎的架构和研发。