lambda 表达式的语法 :
[捕获]<模板形参>(形参)lambda说明符 约束
- 捕获:
使用前:非局部变量,静态存储期或线程存储期,常量表达式初始化的引用的变量无需捕获
读取前:变量是const 非volatile 的整型或枚举型, 或者是constexpr 的且没有mutable的成员
默认捕获符只有&,= , &是隐式引用捕获, =是复制隐式捕获。两者都能捕获*this, 如果是隐式捕获 那么=的效果为&
单独捕获有10种形式:
捕获 中单独的捕获符的语法是
标识符
标识符 ...
标识符 初始化器
& 标识符
& 标识符 ...
& 标识符 初始化器
this
*this
... 标识符 初始化器
& ... 标识符 初始化器
-
lambda说明符:
由说明符、异常说明、属性、尾随返回类型按照顺序组成
说明符有mutable、constexpr, consteval.mutable表示可修改, constexpr
异常说明就是 noexcept 为 operator() 使用
属性与普通函数 如[[noreturn]] [[nodiscard]]
尾随返回类型就是->指代返回 类型 -
约束:
为operator()提供约束
如果形参中有auto 那么相当于是提供了模板形参, 是泛型lambda
lambda中的用户定义转换函数:
闭包类型::operator 返回类型(*)(形参)()
这个函数只有捕获为空才会定义, 可以当做函数指针使用, 效果与函数对象相同
lambda的实质就是构造了一个闭包类型
lambda odr式
- lambda odr式使用了以复制捕获的实体, 访问的其实是闭包类型的复制成员
void f(const int*);
void g()
{
const int N = 10;
[=]{
int arr[N]; // 非 ODR 式使用:指代 g 的 const int N
f(&N); // ODR 式使用:导致 N 被(以复制)捕获
// &N 是闭包对象的成员 N 的地址,而非 g 中的 N
}();
}
- lambda odr式使用了以引用捕获的实体,访问的是原引用所指代的对象而非引用本身
#include <iostream>
auto make_function(int& x) {
return [&]{ std::cout << x << '\n'; };
}
int main() {
int i = 3;
auto f = make_function(i); // f 中对 x 的使用直接绑定到 i
i = 5;
f(); // OK;打印 5
}
- 在= 捕获操作中,如果用如decltype捕获了它的类型,等同于被捕获
void f3() {
float x, &r = x;
[=]
{ // x 与 r 不被捕获(在 decltype 的操作数中出现并不是 ODR 式使用)
decltype(x) y1; // y1 拥有 float 类型
decltype((x)) y2 = y1; // y2 拥有 float const& 类型,因为此 lambda
// 非 mutable 且 x 是左值
decltype(r) r1 = y1; // r1 拥有 float& 类型(不考虑变换)
decltype((r)) r2 = y2; // r2 拥有 float const& 类型
};
}
用this或this捕获的lambda的外围函数必须是类的非静态成员函数, 要么处在某个成员默认初始化器中
struct s2 {
double ohseven = .007;
auto f() { // 以下两个 lambda 的最接近外围函数
return [this] { // 以引用捕获外围的 s2
return [*this] { // 以复制捕获外围的 s2 (C++17)
return ohseven; // OK
}
}();
}
auto g() {
return []{ // 无捕获
return [*this]{}; // 错误:*this 未被外层 lambda 表达式所捕获
}();
}
};
- 如果odr式使用了自动存储期的变量,那么必须捕获
void f1(int i)
{
int const N = 20;
auto m1 = [=] {
int const M = 30;
auto m2 = [i] {
int x[N][M]; // N 与 M 未被 ODR 式使用
// (它们可以不被捕获)
x[0][0] = i; // i 被 m2 显式捕获
// 并被 m1 隐式捕获
};
};
struct s1 // f1() 中的局部类
{
int f;
void work(int n) // 非静态成员函数
{
int m = n * n;
int j = 40;
auto m3 = [this, m] {
auto m4 = [&, j] { // 错误:j 未被 m3 捕获
int x = n; // 错误:n 被 m4 隐式捕获
// 但未被 m3 捕获
x += m; // OK:m 被 m4 捕获
// 且被 m3 显式捕获
x += i; // 错误:i 在可达作用域之外
// (该作用域在 work() 结束)
x += f; // OK:this 被 m4 隐式捕获
// 且被 m3 显式捕获
};
};
}
};
}
*不带有初始化器的捕获符不能捕获类成员
class S {
int x = 0;
void f() {
int i = 0;
// auto l1 = [i, x]{ use(i, x); }; // 错误:x 不是变量
auto l2 = [i, x=x]{ use(i, x); }; // OK,复制捕获
i = 1; x = 1; l2(); // 调用 use(0,0)
auto l3 = [i, &x=x]{ use(i, x); }; // OK,引用捕获
i = 2; x = 2; l3(); // 调用 use(1,2)
}
};
*隐式以=捕获类成员时, 实际未产生副本,而是以this->形式
class S {
int x = 0;
void f() {
int i = 0;
// auto l1 = [i, x]{ use(i, x); }; // 错误:x 不是变量
auto l2 = [i, x=x]{ use(i, x); }; // OK,复制捕获
i = 1; x = 1; l2(); // 调用 use(0,0)
auto l3 = [i, &x=x]{ use(i, x); }; // OK,引用捕获
i = 2; x = 2; l3(); // 调用 use(1,2)
}
};
如果 lambda 表达式在默认实参中出现,那么它不能显式或隐式捕获任何变量。