提问人:Emil 提问时间:8/11/2021 最后编辑:Emil 更新时间:8/11/2021 访问量:263
为什么不可变 lambda 中的字段在捕获 const 值或 const 引用时使用“const”?
Why do fields in non-mutable lambdas use "const" when capturing const values or const references?
问:
如问题 lambda capture by value mutable does not work with const &?,当使用其名称或在可变 lambda 中捕获类型的值时,隐藏类中的字段会获取 type 。可以说,对于可变 lambda 来说,这是正确的做法。const T&
[=]
const T
但是,为什么对不可变的 lambda 也要这样做呢?在不可变的 lambda 中,声明了 ,因此它无论如何都无法修改捕获的值。operator()(...)
const
当我们移动 lambda 时,就会发生这种情况的不良后果,例如,将其包装在 .std::function
请参阅以下两个示例:
#include <cstdio>
#include <functional>
std::function<void()> f1, f2;
struct Test {
Test() {puts("Construct");}
Test(const Test& o) {puts("Copy");}
Test(Test&& o) {puts("Move");}
~Test() {puts("Destruct");}
};
void set_f1(const Test& v) {
f1 = [v] () {}; // field type in lambda object will be "const Test"
}
void set_f2(const Test& v) {
f2 = [v = v] () {}; // field type in lambda object will be "Test"
}
int main() {
Test t;
puts("set_f1:");
set_f1(t);
puts("set_f2:");
set_f2(t);
puts("done");
}
我们得到以下编译器生成的 lambda 类:
class set_f1_lambda {
const Test v;
public:
void operator()() const {}
};
class set_f2_lambda {
Test v;
public:
void operator()() const {}
};
程序打印以下内容(使用 gcc 或 clang):
Construct
set_f1:
Copy
Copy
Copy
Destruct
Destruct
set_f2:
Copy
Move
Move
Destruct
Destruct
done
Destruct
Destruct
Destruct
在第一个示例中,该值被复制不少于三次。v
set_f1
在第二个示例中,唯一的副本是在捕获值时(如预期的那样)。使用两个移动的事实是 libstdc++ 中的一个实现细节。当按值将函子传递给内部函数时,第一个移动发生在内部(为什么这个函数签名不使用引用传递?第二个移动发生在移动构造最终堆分配的函子时。set_f2
operator=
std::function
但是,如果字段是字段,则 lambda 函子对象的 move 构造函数不能对字段使用 moveconstructor(因为此类构造函数在窃取其内容后无法“清除”常量变量)。这就是为什么必须对此类字段使用复制构造函数的原因。const
所以对我来说,它似乎只对捕获不可变的 lambda 中的值产生负面影响。我是否错过了一些重要的东西,或者它只是以这种方式标准化以使标准更简单?const
答:
我是否错过了一些重要的东西,或者它只是以这种方式标准化以使标准更简单?
原始的 lambda 提案,
区分捕获对象的类型和 lambda 闭包类型的相应数据成员的类型:
/6 闭包对象的类型是具有唯一名称的类,称为 F,认为是在 发生 lambda 表达式。
在上下文中查找有效捕获集中的每个名称 N 其中 lambda 表达式似乎用于确定其对象类型;对于引用,对象类型是引用所引用的类型。对于有效捕获集中的每个元素,F 具有私有非静态数据成员,如下所示:
- 如果元素是 this,则数据成员具有某个唯一名称,将其命名为 t,并且属于 this ([class.this], 9.3.2);
- 如果元素的形式为 & N,则数据成员的名称为 N,并键入“对对象类型的 N 的引用”; 5.19. 常量表达式 3
- 否则,元素的形式为 N,数据成员的名称为 N,键入“cv-unqualified object type of N”。
在这个原始措辞中,OP 的示例不会产生 -qualified data 成员。我们还可以注意到,我们承认这些措辞const
v
对于引用,对象类型是引用所引用的类型
它存在于 [expr.prim.lambda.capture]/10 的 [expr.prim.lambda.capture]/10 中(最新的草案)中关于 lambda 的最终措辞:
如果实体是对对象的引用,则此类数据成员的类型为引用类型,如果实体是对函数的引用,则为对引用函数类型的左值引用,否则为相应捕获实体的类型。
发生的事情是
它重写了 N2550 的大部分措辞:
在 2009 年 3 月的峰会期间,大量与 C++x 相关的问题 Lambda 由核心工作组 (CWG) 提出并审核。在决定清除后 对于大多数问题,CWG得出结论认为,最好重写该部分 在 Lambdas 上实现该方向。本文介绍了这种重写。
特别是,就本问题而言,解决CWG问题
[...]请看以下示例:
void f() { int const N = 10; [=]() mutable { N = 30; } // Okay: this->N has type int, not int const. N = 20; // Error. }
也就是说,作为闭包对象成员的 N 不是 const, 即使捕获的变量是 const。 这似乎很奇怪,因为 捕获基本上是捕获本地环境的一种手段。 避免终生问题的方式。更严重的是,类型的变化 表示 decltype、重载解析和模板的结果 应用于 lambda 中捕获的变量的参数推导 表达式可以与包含 lambda 表达式,这可能是 bug 的微妙来源。
之后,措辞(截至 N2927)被制成我们最终看到的 C++ 11
此类数据成员的类型 是相应捕获实体的类型,如果该实体不是对 对象,否则为引用类型。
如果我敢推测,CWG 756的决议还意味着保留简历限定符,用于参考类型的实体的价值捕获,这可以说是一个疏忽。
评论
mutable
mutable
const
operator()(...)
const T&
评论