这个问题不能用代码完全回答。您也许可以编写一些“等效”的代码,但是没有以这种方式指定标准。
不用担心,让我们深入了解[expr.prim.lambda]
。首先要注意的是,仅在[expr.prim.lambda.closure]/13
以下内容中提到了构造函数:
与相关联的闭合类型λ-表达没有默认的构造,如果λ-表达具有λ-捕获和否则一个默认的默认构造函数。它具有默认的副本构造函数和默认的move构造函数([class.copy.ctor])。如果lambda-expression具有lambda-capture,并且具有默认的复制和移动分配操作符,则它具有已删除的复制分配操作符([class.copy.assign])。[ 注意:这些特殊的成员函数照常隐式定义,因此可能定义为已删除。— 尾注 ]
因此,马上就要搞清楚了,构造函数并没有正式地定义捕获对象的方式。您可以很接近(请参阅cppinsights.io答案),但是细节有所不同(请注意案例4中该答案中的代码如何不编译)。
这些是讨论案例1所需的主要标准条款:
[expr.prim.lambda.capture]/10
[...]
对于每个通过副本捕获的实体,在闭包类型中声明了一个未命名的非静态数据成员。这些成员的声明顺序未指定。如果实体是对对象的引用,则此数据成员的类型为引用的类型;如果实体是对函数的引用,则为对引用的函数类型的左值引用;否则为相应捕获的实体的类型。匿名工会的成员不得被抄袭。
[expr.prim.lambda.capture]/11
lambda 表达式的复合语句中的每个id 表达式(它是对通过副本捕获的实体的odr用法)都被转换为对闭包类型的相应未命名数据成员的访问。[...]
[expr.prim.lambda.capture]/15
评估lambda表达式时,将使用通过复制捕获的实体直接初始化生成的闭包对象的每个对应的非静态数据成员,并将与init-capture对应的非静态数据成员初始化为由相应的初始化程序(可以是复制初始化或直接初始化)指示。[...]
让我们将其应用于您的情况1:
情况1:按值捕获/默认值捕获
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
此lambda的闭包类型将具有__x
类型int
(因为x
既不是引用也不是函数)的未命名的非静态数据成员(我们称之为),并且x
lambda主体内的访问将转换为对的访问__x
。当我们评估lambda表达式(即分配到的时候lambda
),我们直接初始化 __x
用x
。
简而言之,仅复制一份。不涉及闭包类型的构造函数,并且不可能用“普通” C ++来表示(请注意,闭包类型也不是聚合类型)。
参考记录涉及[expr.prim.lambda.capture]/12
:
如果实体是隐式或显式捕获的,但不是通过副本捕获的,则通过引用捕获该实体。对于通过引用捕获的实体,是否在关闭类型中声明了其他未命名的非静态数据成员,尚不确定。[...]
关于引用的引用捕获,还有另一段内容,但是我们在任何地方都没有这样做。
因此,对于情况2:
情况2:按引用捕获/默认按引用捕获
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
我们不知道是否将成员添加到闭包类型。x
在lambda主体中可能只是直接引用x
外部。这是由编译器确定的,它将以某种形式的中间语言(这在编译器之间是不同的)而不是C ++代码的源代码转换来完成的。
初始化捕获的详细信息[expr.prim.lambda.capture]/6
:
初始化捕获的行为就像声明并显式捕获形式auto init-capture ;
的变量一样,该变量的声明性区域是lambda表达式的复合语句,除了:
- (6.1)如果捕获是通过副本进行的(请参见下文),则为捕获声明的非静态数据成员和变量被视为引用同一对象的两种不同方式,这具有非静态数据的生存期成员,并且不会执行其他复制和销毁操作,并且
- (6.2)如果捕获是通过引用进行的,则变量的生存期将在闭包对象的生存期结束时结束。
鉴于此,让我们看一下情况3:
情况3:通用init捕获
auto lambda = [x = 33]() { std::cout << x << std::endl; };
如前所述,将其想象为一个auto x = 33;
由副本创建并由副本明确捕获的变量。此变量仅在lambda主体内“可见”。正如指出的[expr.prim.lambda.capture]/15
前面,闭合型(的对应部件的初始化__x
为后代)是由初始值设定在所述lambda表达式的评估给定的。
为避免疑问:这并不意味着此处将事物初始化两次。的auto x = 33;
是一个“如同”继承简单捕获的语义,并且所描述的初始化是这些语义的变形例。仅发生一次初始化。
这还涉及情况4:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
闭包类型成员是通过__p = std::move(unique_ptr_var)
何时对lambda表达式求值(即何时l
赋给)来初始化的。访问p
在lambda体被变换成访问__p
。
TL; DR:仅执行最少数量的复制/初始化/移动(正如人们希望/期望的那样)。我会假设未按源转换的形式指定lambda (与其他语法糖不同)的原因恰恰是因为以构造函数的形式表示事物将需要多余的操作。
我希望这可以解决问题中表达的担忧:)