lambda with template

2023-09-05 18:19:52 浏览数 (1)

你好,我是雨乐!

好久没发文了,一方面因为实在太忙了,一方面也在自我提升,发现要学的越来越多。在群里的,大抵都知道我上半年基本在重构和优化,将gcc版本进行了升级,从之前的4.9.2升级到了现在的11.2.1(支持c 17),这样在重构过程中难免要用到新特性,所以也就在这个过程中一边学习一遍应用于项目中,毕竟这样才能完全吸收为自己所用嘛。

在重构的过程中,某些使用cpp11往往需要几十行甚至上百行实现的功能,用17进行重写也就那么十几行完事,真的太方便了,今天就聊聊用过的一个比较爽的功能lambda with template,好了,开始正文吧。

从一个例子开始

现在有一个小需求,就是用lambda来实现遍历一个std::vector<>,很简单吧,我想很多人会这么写:

代码语言:javascript复制
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支持,代码如下:

代码语言:javascript复制
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意味着参数可以是任何类型,甚至是一个字符串,如下:

代码语言:javascript复制
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,遂使用该特性解决上面这个问题:

代码语言:javascript复制
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: 对于consexpr这块不是很了解的话,建议看上一篇文章性能优化利器之constexpr

好了,截止到现在,上面的示例代码中lambda支持了多类型的std::vector以及传入非vector数据(比如上例中的int),那么有没有一种更简单的方式,支持多类型的std::vector呢?这就引入了lambda的新特性-template,废话不多说,直接上代码:

代码语言:javascript复制
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

在项目中有这样一个模块,针对不同服务返回的数据,进行不同的处理,现有代码实现如下:

代码语言:javascript复制
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实现的话,该怎么做呢?相信看了前面的内容,看到这个问题,可以说是手到拿来了,看代码:

代码语言:javascript复制
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罢了。。。

今天的文章就到这,我们下期见!

推荐阅读 点击标题可跳转

1、我们通常说的POD到底是什么?

2、心心念念的优化完成了,虽然不是很完美

3、从一次字符串拼接失败说起

0 人点赞