Emi*_*mil 7 c++ lambda constants mutable language-lawyer
正如问题lambda capture by value mutable 中所见,它不适用于 const &? , 当const T&使用其名称或[=]可变 lambda捕获类型的值时,隐藏类中的字段获取类型const T。可以争论的是,这对于可变 lambda 来说是正确的做法。
但是为什么对非可变 lambda 也这样做呢?在非可变 lambda 表达式中,operator()(...)is 被声明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");
}
Run Code Online (Sandbox Code Playgroud)
我们得到以下编译器生成的 lambda 类:
class set_f1_lambda {
const Test v;
public:
void operator()() const {}
};
class set_f2_lambda {
Test v;
public:
void operator()() const {}
};
Run Code Online (Sandbox Code Playgroud)
该程序打印以下内容(使用 gcc 或 clang):
Construct
set_f1:
Copy
Copy
Copy
Destruct
Destruct
set_f2:
Copy
Move
Move
Destruct
Destruct
done
Destruct
Destruct
Destruct
Run Code Online (Sandbox Code Playgroud)
v在第一个示例中,该值被复制不少于 3 次set_f1。
在第二个示例中set_f2,唯一的副本是在捕获值时(如预期的那样)。使用两次移动这一事实是 libstdc++ 中的一个实现细节。当按值将函子传递给内部函数时,第一步发生在operator=instd::function内部(为什么此函数签名不使用按引用传递?)。第二个移动发生在移动构造最终堆分配的函子时。
但是,如果字段是 const(因为这样的构造函数在窃取其内容后无法“清除”const 变量)。这就是为什么必须将复制构造函数用于此类字段的原因。
所以对我来说,像const在非可变 lambdas 中一样捕获值似乎只有负面影响。我是否遗漏了一些重要的东西,或者它只是通过这种方式标准化以某种方式使标准更简单?
\n\n我是否错过了一些重要的事情,或者只是以这种方式标准化以使标准更加简单?
\n
最初的 lambda 提案,
\n\n区分捕获对象的类型和 lambda 闭包类型的相应数据成员的类型:
\n\n\n/6 闭包对象的类型是一个具有唯一名称的类,称之为 F,被认为是在 lambda 表达式出现的位置定义的。
\n有效捕获集中的每个名称 N 在 lambda 表达式出现的上下文中查找,以确定其对象类型;\n如果是引用,则对象类型是引用所引用的类型。对于有效捕获集中的每个元素,F\n都有一个私有非静态数据成员,如下所示:
\n\n
\n- 如果元素是 this,则数据成员有一些唯一的名称,称之为 t,并且是 this 的类型 ([class.this],\n9.3.2);
\n- 如果元素的形式为 & N,则数据成员的名称为 N,类型为 \xe2\x80\x9c,对 N\xe2\x80\x9d 的对象类型的引用;\n5.19。常数表达式 3
\n- 否则,元素的形式为 N,数据成员的名称为 N,类型为 \xe2\x80\x9c N\xe2\x80\x9d 的cv 非限定对象类型。
\n
在这个原始措辞中,OP 的示例不会产生const- 限定的数据成员v。我们还可能注意到,我们认识到这样的措辞
\n\n对于引用,对象类型是引用所引用的类型
\n
它存在于 lambda 的最终措辞(最新草案)的[expr.prim.lambda.capture]/10中(但直接说明数据成员的类型而不是对象类型) :
\n\n\n如果实体是对对象的引用,则此类数据成员的类型是引用类型;如果实体是对函数的引用,则此类数据成员的类型是对引用函数类型的左值引用,否则是相应捕获实体的类型。
\n
发生的事情是
\n\n重写了N2550的大部分措辞:
\n\n\n在 2009 年 3 月的 Summit 会议上,核心工作组 (CWG) 提出并审查了与 C++0x\nLambda 相关的大量问题。在为大多数问题确定了明确的方向后,CWG 得出的结论是,最好重写 Lambda 部分来实现该方向。本文介绍了这种重写。
\n
特别是,就该问题而言,解决 CWG 问题
\n\n\n\n[...]考虑以下示例:
\nRun Code Online (Sandbox Code Playgroud)\nvoid f() {\n int const N = 10;\n [=]() mutable { N = 30; } // Okay: this->N has type int, not int const.\n N = 20; // Error.\n}\n也就是说,作为闭包对象成员的 N 不是 const,\n即使捕获的变量是 const。这看起来很奇怪,因为捕获基本上是一种捕获本地环境的方法,可以避免生命周期问题。更严重的是,类型的更改意味着应用于 lambda 表达式内捕获变量的 decltype、重载解析和模板参数推导的结果可能与包含 lambda 表达式的作用域中的结果不同,这可能是错误的微妙来源。
\n
之后,该措辞(截至 N2927)被制成我们最终看到的 C++11 措辞
\n\n\n如果实体不是对对象的引用,则此类数据成员的类型\n是相应捕获实体的类型,否则是引用的类型。
\n
如果我敢推测,CWG 756 的决议还意味着保留引用类型实体的价值捕获的 cv 限定符,这可能是一种疏忽。
\n| 归档时间: |
|
| 查看次数: |
130 次 |
| 最近记录: |