Lambda 捕获数组元素失败
Lambda capture an array element failed
下面的 C++ 代码让编译器 GCC(6.3.0) 和 Clang (3.8.0) 抓狂。
for (auto v : vectors2d)
for_each (begin(ret), end(ret), [v[3]] (int &n) { n+= v[3];});
虽然以下没问题
for (auto v : vectors2d) {
auto val = v[3];
for_each (begin(ret), end(ret), [val] (int &n) { n+= val;});
}
我知道在 C++ 14 中我可以做类似的事情
for (auto v : vectors2d)
for_each (begin(ret), end(ret), [val=v[3]] (int &n) { n+= val;});
GCC 中的错误是
expected identifier before '[' token
叮当说
expected ',' or ']' in lambda capture list
我的问题是:为什么 [v[3]]
出现在捕获列表中是非法的?
My question is why it is illegal for [v[3]]
in capture list?
如 N4141 中的 5.1.2/1 [expr.prim.lambda] 所述,捕获列表中的项目应为 simple-capture 或 初始化捕获.
前者为
- 标识符
&
标识符
this
,
后者 标识符初始化器 或 &
标识符初始化器.
v[3]
符合上面的 none,因此被编译器正确拒绝。
v[3]
不是变量 - 它是展开为 *(v + 3)
的复杂表达式(如果 operator[]
未重载)。因此,捕获 v[3]
在本质上与捕获 x * x + y * y
非常相似——而且意义不大。例如。编译器必须在 lambda 内部接受 x * x + y * y
,但有时会拒绝 y * y + x * x
,因为重载运算符不必是可交换的。
基本上,你问编译器:"if I use an expression equivalent to what I captured, it's ok, but if I mix variables the other way around, you should give me compiler error".
假设 v[3]
是合法的。然后应该正确编译以下所有 lambda:
[v[3]]() { return v[3]; }
[v[3]]() { return v[2 * 2 - 1]; }
[v[3]](int x) { assert(x == 3); return v[x]; }
因此,如果我们希望 "invalid capture" 成为编译器错误,编译器应该能够以某种方式 "prove" 我们不会访问 v
中的任何元素,除了v[3]
。这比停机问题更难,所以不可能。
当然,我们可以做一些不太严格的限制:例如只允许 v[3]
,但不允许 v[2 * 2 - 1]
,或者创建一些算法来检测这种有效的情况 "good enough",但有时会提供假阴性。我认为这不值得付出努力 - 您总是可以 "cache" 变量中的表达式并按值捕获它。
下面的 C++ 代码让编译器 GCC(6.3.0) 和 Clang (3.8.0) 抓狂。
for (auto v : vectors2d)
for_each (begin(ret), end(ret), [v[3]] (int &n) { n+= v[3];});
虽然以下没问题
for (auto v : vectors2d) {
auto val = v[3];
for_each (begin(ret), end(ret), [val] (int &n) { n+= val;});
}
我知道在 C++ 14 中我可以做类似的事情
for (auto v : vectors2d)
for_each (begin(ret), end(ret), [val=v[3]] (int &n) { n+= val;});
GCC 中的错误是
expected identifier before '[' token
叮当说
expected ',' or ']' in lambda capture list
我的问题是:为什么 [v[3]]
出现在捕获列表中是非法的?
My question is why it is illegal for
[v[3]]
in capture list?
如 N4141 中的 5.1.2/1 [expr.prim.lambda] 所述,捕获列表中的项目应为 simple-capture 或 初始化捕获.
前者为
- 标识符
&
标识符this
,
后者 标识符初始化器 或 &
标识符初始化器.
v[3]
符合上面的 none,因此被编译器正确拒绝。
v[3]
不是变量 - 它是展开为 *(v + 3)
的复杂表达式(如果 operator[]
未重载)。因此,捕获 v[3]
在本质上与捕获 x * x + y * y
非常相似——而且意义不大。例如。编译器必须在 lambda 内部接受 x * x + y * y
,但有时会拒绝 y * y + x * x
,因为重载运算符不必是可交换的。
基本上,你问编译器:"if I use an expression equivalent to what I captured, it's ok, but if I mix variables the other way around, you should give me compiler error".
假设 v[3]
是合法的。然后应该正确编译以下所有 lambda:
[v[3]]() { return v[3]; }
[v[3]]() { return v[2 * 2 - 1]; }
[v[3]](int x) { assert(x == 3); return v[x]; }
因此,如果我们希望 "invalid capture" 成为编译器错误,编译器应该能够以某种方式 "prove" 我们不会访问 v
中的任何元素,除了v[3]
。这比停机问题更难,所以不可能。
当然,我们可以做一些不太严格的限制:例如只允许 v[3]
,但不允许 v[2 * 2 - 1]
,或者创建一些算法来检测这种有效的情况 "good enough",但有时会提供假阴性。我认为这不值得付出努力 - 您总是可以 "cache" 变量中的表达式并按值捕获它。