C++ Lambda表达式第二篇, Lambda表达式

发布于:2024-07-09 ⋅ 阅读:(142) ⋅ 点赞:(0)

Lambda 捕获

captures是零个或多个捕获的逗号分隔列表,可以选择以capture-default开头。捕获列表定义可从 lambda 函数体内访问的外部变量。唯一的捕获默认值是

  • &,变量引用方式,隐式捕获已使用的变量,存储持续时间限于Lambda表达式内
  • =,值引用方式,隐式捕获已使用的变量,存储持续时间限于Lambda表达式内。

如果存在任一捕获默认值,则当前对象 (*this)可以隐式捕获。如果隐式捕获,则始终通过引用捕获,即使捕获默认值为=。从C++20开始,当捕获默认值为 = 时,不推荐使用 *this 的隐式捕获。

captures 中单个捕获的语法是

下面展示一些 内联代码片

 - identifier                            简单的值引用捕获
 - identifier ...                        简单的值引用捕获,捕获是一个包扩展
 - identifier initializer                简单的值引用捕获,捕获包括初始化器
 - &identifier                           简单的变量引用捕获
 - &identifier...                        简单的变量引用捕获,捕获是一个包扩展
 - &identifier initializer               简单的变量引用捕获,捕获包括初始化器
 - this                                  当前对象的简单的变量引用捕获
 - *this                                 当前对象的简单的值引用捕获
 - ...identifier initializer             值引用捕获,捕获是一个包扩展, 包扩展包含初始化器
 - &...identifier initializer            变量引用捕获,捕获是一个包扩展, 包扩展包含初始化器

如果capture-default 为 &,则后续的简单捕获不能以 & 开头。

#include  <iostream>

using namespace std;

struct S2 { void f(int i, int j); };
void S2::f(int i, int j)
{
	[&] {i = i + 1;};          // OK: by-reference capture default
	cout << "i = " << i << endl;
	
//	[&, i] {i=0; j=j+1;};       // error: assignment of read-only variable ‘i’

	[&, i] {j=i+1;};
	cout << "j=" << j << endl;
	
//	[&, &i] {};      // Error: by-reference capture when by-reference is the default
	[=, &j] {j=i+1;};
	cout << "j=" << j << endl;
	
	[&, this, i] {}; // OK, equivalent to [&, i]
}


int main() {	
	S2 s1;
	s1.f(1, 2);
   cout << endl;
	
	s1.f(9, 10);
}

代码运行的屏幕输出

i = 1
j=2
j=2

i = 9
j=10
j=10

只有在块作用域或默认成员初始器中定义的 lambda 表达式,才可以有一个默认捕获或不带初始器的捕获。对于此类 lambda 表达式,其有效范围定义为一组封闭范围,这个范围包括最内层封闭函数(及其参数)。这包括嵌套的块作用域和封闭 lambda 的作用域(如果该 lambda 是嵌套的)。

任何没有初始化程序的捕获(除了 this-capture)中的 identifier,都是在 lambda 的到达范围内,使用通常的非限定名称查找来查找的。查找的结果必须是,在到达范围内声明的具有自动存储持续时间的变量,或者是其对应变量满足此类要求的结构化绑定。该实体被显式捕获。

带初始器的捕获的行为,就好像它声明,并显式捕获一个变量,这个变量使用类型说明符 auto 和相同的初始器声明。其声明区域是 lambda 表达式的主体(即,它不在其初始器设定的范围内),但不包括下列情况:

  • 如果捕获是通过值引用,则闭包对象引入的非静态数据成员,是引用该变量的另一种方式;
    换句话说,源变量实际上并不存在,并且通过 auto 进行类型推导和初始化,适用于该非静态数据成员;
  • 如果捕获是通过变量引用,则闭包对象的生命周期结束时,引用变量的生命周期也结束。

这用于通过 x = std::move(x) 等捕获,来捕获仅移动类型。
这也使得可以使用 &cr = std::as_const(x) 或类似方法,使const 引用捕获成为可能。

#include  <iostream>

using namespace std;

int x = 4;
auto y = [&r = x, x = x + 1]()->int{r += 2; return x * x;};

int main()
{
	auto val = y();
	
	cout << "x=" << x  <<  endl;
	cout << val << endl;
}
x=6
25

如果捕获表有一个默认捕获,并且未显式捕获封闭对象(如 this 或 *this),或者自动变量,或者结构化绑定;那么,它会隐式捕获实体, 该实体在一个表达式中,在potentially-evaluated表达式中命名。
该自动变量在lambda 主体中是odr-usable。结构化绑定有原子存储持续时间。

#include  <iostream>

using namespace std;

void f(int x, const int (&)[2] = {}) { cout <<"f1:" << x <<endl;}   // #1
void f(const int &x, const int (&)[1]) { cout <<"f2:" << x <<endl;} // #2
 
struct NoncopyableLiteralType
{
    constexpr explicit NoncopyableLiteralType(int n) : n_(n) {}
    NoncopyableLiteralType(const NoncopyableLiteralType&) = delete;
 
    int n_;
};
    
int main ()
{
    const int x = 17;
 
    auto l0 = []{ f(x); };           // OK: calls #1, does not capture x
    auto g0 = [](auto a) { f(x); };  // same as above
 
	auto l1 = [=]{ f(x); };          // OK: captures x (since P0588R1) and calls #1
                                     // the capture can be optimized away
	auto g1 = [=](auto a) { f(x); }; // same as above
	auto g2 = [=](auto a) {
		int selector[sizeof(a) == 1 ? 1 : 2] = {};
		f(x, selector); 					// OK: is a dependent expression, so captures x
		cout << "g2" << endl;
	};

	auto ltid = [=]{ return typeid(x).name(); };   // OK: captures x (since P0588R1)
                                     // even though x is unevaluated
                                     // the capture can be optimized away
	auto g3 = [=](auto a) {
		auto type = typeid(a + x).name();  // captures x regardless of
                        // whether a + x is an unevaluated operand
		cout << "g3: " << type << endl;
	};

	auto g4 = [&](auto a) {
		int selector[sizeof(a) == 1 ? 1 : 2] = {};
		f(x, selector); 					// OK: is a dependent expression, so captures x
		cout << "g2" << endl;
	};

	l1();
	
	g1(1);
  
	g2(1);
	g2(2);
	cout << "x id:" << ltid() << endl;
	g3(4);
	g4(1);
	 
    constexpr NoncopyableLiteralType w{42};
    auto l4 = []{ return w.n_; };      // OK: w is not odr-used, capture is unnecessary
    // auto l5 = [=]{ return w.n_; };  // error: w needs to be captured by copy
	 cout << l4() << endl;
}

f1:17
f1:17
f1:17
g2
f1:17
g2
x id:i
g3: i
f1:17
g2
42

含有模板参数的Lambda表达式

 - [captures ] front-attr (optional) (params ) specs (optional) exception (optional) back-attr (optional) trailing-type (optional) requires (optional) { body }
 - [captures ] { body }
 - [captures ] front-attr (optional) trailing-type (optional) { body }	
 - [captures ] front-attr (optional) exception back-attr (optional) trailing-type (optional) { body }	
 - [captures ] front-attr (optional) specs exception (optional) back-attr (optional) trailing-type (optional) { body }
  • captures - Lambda表达式可以拥有零个或多个逗号分隔的捕获列表,可以选择以捕获默认值开头。
    lambda 表达式可以使用变量而不捕获它,如果该变量
  • 是非局部变量或具有静态或线程局部存储持续时间(在这种情况下无法捕获该变量),或者
  • 是已使用常量表达式初始化的引用。

lambda 表达式可以读取变量的值而不捕获它,如果变量

  • 具有 const 非易失性整型或枚举类型,并且已使用常量表达式进行初始化,或者

  • 是 constexpr 并且没有可变成员。

  • tparams - 模板参数的非空逗号分隔列表,用于为通用 lambda 的模板参数提供名称。

  • t-requires - 给tparams 添加约束。
    如果 t-requires 以属性说明符序列结尾,则序列中的属性将被视为 front-attr 中的属性。

  • front-attr - 属性说明符序列适用于闭包类型的operator()。

  • params - 闭包类型的operator()的参数列表。
    它可以有一个显式的对象参数。

  • specs - 以下说明符的列表,每个说明符在每个序列中最多允许出现一次。

说明符 意义
mutable 允许 body 修改通过复制捕获的对象,并调用它们的非常量成员函数。如果存在显式对象参数,则无法使用。
constexpr 显式指定 operator() 是 constexpr 函数。如果 operator() 满足所有 constexpr 函数要求,则即使 constexpr 不存在,operator() 也将是constexpr 。
consteval 指定 operator() 是立即函数。consteval 和 constexpr 不能同时使用。
static 指定 operator() 是静态成员函数。static 和 mutable 不能同时使用。如果 captures 不为空,或者存在显式对象参数,则无法使用。
  • exception - 为闭包类型的operator()提供动态异常规范,或noexcept说明符。

  • back-attr - 属性说明符序列适用于闭包(Closure)类型的operator()类型(因此不能使用noreturn属性)。

  • Trailing-type - -> ret,其中 ret 指定返回类型。

  • require - 给闭包类型的 operator() 添加约束。

  • body - 函数体。

在 body 的开头隐式定义 __func __

无模板参数的Lambda表达式

 - [captures ] <tparams > t-requires (optional) front-attr (optional) (params ) specs (optional) exception (optional) back-attr (optional) trailing-type (optional) requires (optional) { body }
 - [captures ] <tparams > t-requires (optional) { body }
 - [captures ] <tparams > t-requires (optional) front-attr (optional) trailing-type (optional) { body }	
 - [captures ] <tparams > t-requires (optional) front-attr (optional) exception back-attr (optional) trailing-type (optional) { body } 
 - [captures ] <tparams > t-requires (optional) front-attr (optional) specs exception (optional) back-attr (optional) trailing-type (optional) { body }

网站公告

今日签到

点亮在社区的每一天
去签到