【问题标题】:Lambda capture an array element failedLambda 捕获数组元素失败
【发布时间】:2017-06-11 14:38:13
【问题描述】:

下面的 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]] 出现在捕获列表中是非法的?

【问题讨论】:

  • 我很确定 [val=v[3]] 自 C++14 以来是合法的。
  • 您也可以通过引用捕获整个v
  • 是的,val=v[3] 在 C++14 中是合法的。
  • 那你问的是什么版本? C++11?
  • [v[3]] 在我认为的所有版本中都是非法的

标签: c++ gcc lambda language-lawyer clang++


【解决方案1】:

我的问题是为什么[v[3]] 在捕获列表中是非法的?

如 N4141 中 5.1.2/1 [expr.prim.lambda] 中所述,捕获列表中的项目应为 simple-captureinit-capture。

前者是任何一个

  • 标识符
  • & 标识符
  • this,

后者可以是标识符初始化器&标识符初始化器

v[3] 不符合上述条件,因此被编译器正确拒绝。

【讨论】:

  • @gsamaras 这是一个标识符,后跟一个初始化程序。与val=v[3] 类似,val 是标识符,=v[3] 是初始化器。
【解决方案2】:

v[3] 不是一个变量——它是一个复杂的表达式,展开为*(v + 3)(如果operator[] 没有重载)。因此,捕获v[3] 在其精神上与捕获x * x + y * y 非常相似——而且意义不大。例如。编译器必须在 lambda 中接受 x * x + y * y,但有时会拒绝 y * y + x * x,因为重载的运算符不必是可交换的。

基本上,你问编译器:“如果我使用与我捕获的表达式等效的表达式,那没关系,但如果我反过来混合变量,你应该给我编译器错误”。

假设v[3] 是合法的。那么以下所有的 lambdas 都应该被正确编译:

[v[3]]() { return v[3]; }
[v[3]]() { return v[2 * 2 - 1]; }
[v[3]](int x) { assert(x == 3); return v[x]; }

因此,如果我们希望“无效捕获”成为编译器错误,编译器应该能够以某种方式“证明”我们不会访问 v 中除 v[3] 之外的任何元素。这比停机问题更难,所以不可能。

当然,我们可以做一些不那么严格的限制:例如只允许v[3],但不允许v[2 * 2 - 1],或者创建一些算法来检测这种情况“足够好”,但有时会提供假阴性。我认为这不值得付出努力 - 您始终可以将表达式“缓存”在变量中并按值捕获它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-04
    • 1970-01-01
    • 1970-01-01
    • 2013-03-24
    • 2018-03-02
    相关资源
    最近更新 更多