语言特性 | 提案 |
Allow lambda capture [=, this] | P0409R2 |
Familiar template syntax for generic lambdas | P0428R2 |
Simplifying implicit lambda capture | P0588R1 |
Default constructible and assignable stateless lambdas | P0624R2 |
Lambdas in unevaluated contexts | P0315R4 |
Allow pack expansion in lambda init-capture | P0780R2 |
P2095R0 | |
Deprecate implicit capture of this via [=] | P0806R2 |
lambda捕获 [=, this]
当隐式捕获时(使用[=]),它总是通过引用捕获。为了消除这种混淆,C++20弃用这种行为,允许更明确的[=,this],但[&]保持不变
捕获 是零或更多捕获符的逗号分隔列表,可选地以 默认捕获符 开始。仅有的默认捕获符是
- &(以引用隐式捕获被使用的自动变量)
- = (以复制隐式捕获被使用的自动变量)
#include <iostream> #include <type_traits> struct LT{ void f(){ [=]{ std::cout << typeid(this).name() << this->val << std::endl; }(); //从 C++20弃用, 传引用捕获 this } void g(){ [=, *this]{ std::cout << typeid(this).name() << this->val << std::endl; }(); // 从 C++17, 传值捕获 this } void h(){ [=, this]{ std::cout << typeid(this).name() << this->val << std::endl; }(); //从 C++20, 传引用捕获 this } int val = 1; }; int main() { LT lt; lt.f(); lt.g(); lt.h(); return 0; }
在线编译运行
通用lambda的模板参数列表
C++20允许使用熟悉的模板函数语法直接引入类型。
#include <iostream> #include <type_traits> #include <vector> // 完美转发 template <typename... T> void print(T &&... t) { (std::cout << ... << t) << std::endl; } int main() { std::vector<int> ivec = {0, 1, 2, 3, 4, 5}; // lambda 期望 std::vector<T> // 在 C++20前 [](auto vec){ using T = typename decltype(vec)::value_type; for(auto& v : vec) { T t = v; std::cout << t << std::endl; } }(ivec); // 从 C++20后 []<typename T>(std::vector<T> vec){ for(auto& v : vec) { std::cout << v << std::endl; } }(ivec); // 使用参数类型 // 在 C++20前 [](const auto& x){ using T = std::decay_t<decltype(x)>; T copy = x; using Iterator = typename T::const_iterator; Iterator iter = x.cbegin(); std::cout << *iter << std::endl; }(ivec); // 从 C++20后 []<typename T>(const T& x){ T copy = x; using Iterator = typename T::const_iterator; Iterator iter = x.cbegin(); std::cout << *iter << std::endl; }(ivec); // 完美转发 // 在 C++20前 [](auto&&... args){ print(std::forward<decltype(args)>(args)...); }(1, 2.2, 'c'); // 从 C++20后 []<typename... Ts>(Ts&&... args){ print(std::forward<Ts>(args)...); }(1, 2.2, 'c'); // 混合auto和T []<typename T>(const T& a, auto b){ std::cout << a << b << std::endl; }(1,"hello"); return 0; }
在未计算的上下文中
Lambda表达式可以在未计算的上下文中使用,如sizeof()、typeid()、decltype()等。主要的原则是lambdas有一个唯一的未知类型,两个lambdas及其类型永远不相等。
// 以下模板是两个不同的声明 template<class T> void f(decltype([]{}) (*s)[sizeof(T)]); template<class T> void f(decltype([]{}) (*s)[sizeof(T)]);
在下面的例子中,f()在两个翻译单元中增加同一个计数器,因为内联函数的行为就好像它只有一个定义。然而,g_s违反了ODR,因为尽管它只有一个定义,但仍然有多个不同的声明,因为在a.cpp和b.cpp中有两个不同的lambdas,因此,S有不同的非类型模板参数.
a.h
template<typename T> int counter(){ static int value{0}; return ++value; } inline int f(){ return counter<decltype([]{})>(); } template<auto> struct S{ int call(){static int value{0};return ++value;} }; // cast lambda to pointer inline S<+[]{}> g_s;
b.cpp
#include <iostream> #include "a.h" void func(){ auto v = f(); std::cout << "f:" << v << std::endl; int gv = g_s.call(); std::cout << "g:" << gv << std::endl; }
a.cpp
#include <iostream> #include "a.h" void func(); int main() { auto v = f(); std::cout << "f:" << v << std::endl; int gv = g_s.call(); std::cout << "g:" << gv << std::endl; func(); return 0; }
在线运行测试
运行结果:
f:1 g:1 f:2 g:1
默认的可构造和可赋值的无状态lambda
在C++20中,无状态lambda是默认可构造和可赋值的,在未求值上下文中,我们可以通过decltype()获得lambda的类型,并在稍后创建该类型的变量。
例子中,std::map接受一个比较器类型,以便稍后实例化它。虽然我们可以在C++17中获得一个lambda类型,但无法实例化它,因为lambdas不是默认可构造的。
#include <iostream> #include <type_traits> #include <map> int main() { auto greater = [](auto x,auto y){ std::cout << x << y << std::endl; return x > y; }; // 需要是默认可构造的类型 std::map<std::string, int, decltype(greater)> map1; // 需要是默认可赋值的类型 auto map2 = map1; map2["1"] = 1; map2["2"] = 2; map2["3"] = 3; return 0; }
运行结果:
12 21 21 13 23 32 32
在lambda捕获中进行参数包展开
C++20简化了lambda中的参数包捕获。在C++20之前,如果我们想移动包,可以通过值、引用或std::tuple来捕获它们。现在就简单多了,我们可以在初始化捕获中直接捕获参数包。它并不局限于std::move或std::forward,任何函数都可以应用于参数包元素。
#include <iostream> #include <type_traits> #include <map> void f(double, double){std::cout << "fd" << std::endl;} void g(int, int,double){std::cout << "gi" << std::endl;} // C++17 template<class F, class... Args> auto delay_apply(F&& f, Args&&... args) { return [f=std::forward<F>(f), tup=std::make_tuple(std::forward<Args>(args)...)]() -> decltype(auto) { return std::apply(f, tup); }; } // C++20 template<typename F, typename... Args> auto delay_call(F&& f, Args&&... args) { return [f = std::forward<F>(f), ...f_args=std::forward<Args>(args)]() -> decltype(auto) { return f(f_args...); }; } int main() { delay_apply(f, 1.1, 2.2)(); delay_call(g, 1, 2, 3.3)(); return 0; }