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 表达式在默认实参中出现,那么它不能显式或隐式捕获任何变量。