lambda 捕获,捕获“未声明的变量”

lambda capture, capturing an "undeclared variable"

提问人:Oersted 提问时间:6/16/2023 最后编辑:Jan SchultkeOersted 更新时间:6/17/2023 访问量:100

问:

我对这个C++周刊中的一个片段感到惊讶。
我在这里复制了我不明白的部分:

int main() {
    auto accumulator = [sum = 0](int value) mutable {
        sum += value;
        return sum;
    };
    accumulator(1);          // expected return value: 1
    accumulator(2);          // expected return value: 1+2
    return accumulator(-1);  // expected return value: 1+2-1
}

隐式期望(在注释中)已实现,但我不认识此捕获语法:lambda 声明范围内不存在 sum,因此它无法捕获它。
我知道这种语法:它在闭包中声明一个变量,该变量在声明时由声明范围中的值初始化。 此代码正在工作的事实似乎意味着:
[lhs = rhs](whatever){whatever_again};lhsrhs

  • 如果未声明请求的捕获变量,则捕获实际上是一个定义;
  • 调用之间值的持久性意味着它实际上是 lambda 闭包的成员;sum
  • sum不是 const,因为 lambda 是可变的。

然而,我无法将这些假设与 lambda cpppreference 相匹配。

我知道这种语法:它在闭包中声明一个变量,该变量在声明时由声明范围中的值初始化。 上述行为似乎表明它可能只是一个值,并且自动推断出实际类型(我认为我从 ClosureType::Captures 中正确地理解了这一点,下面转载)。我的理解正确吗?有人可以指出参考或解释 cppreference 的措辞吗?
恐怕它提出了第二个问题,这可能是重复的(自由填写以告诉或要求我将帖子分成 2 个):如何在 lambda 主体内确定变量的类型?
[lhs = rhs](whatever){whatever_again};lhsrhsrhslhs

笔记

from ClosureType::Captures: 在 lambda 正文中键入。

每个数据成员的类型是相应捕获实体的类型,除非该实体具有引用类型(在这种情况下,对函数的引用将捕获为对引用函数的左值引用,而对对象的引用将捕获为引用对象的副本)。

也来自 ClosureType::Captures: closure 非静态成员变量:

闭包类型包括未命名的非静态数据成员,这些成员以未指定的顺序声明,这些成员包含如此捕获的所有实体的副本。

恕我直言,如果我将实体概念扩展到任何类型的值,我的假设可能是正确的。lvalue

C++ Lambda 闭包 语言律师

评论

0赞 Aconcagua 6/16/2023
没有任何地方必须是变量,是吗?所以是的,你的理解是正确的。参考措辞:想象一下——你现在期望如何被俘虏?通过引用,因为是引用???当然不是,我们会通过复制来捕获,在没有看到 的声明时符合用户的期望,这也是该段落的要求。rhsint n = ...; int& m = n; [m](){};mmnm
0赞 Oersted 6/16/2023
@RichardCritten 谢谢,可能是我这边的词汇误解。我认为捕获是方括号之间的整个表达式。实际上,括号之间的每个逗号分隔表达式都称为捕获吗?因此,在问题示例中,会是捕获吗?sum
0赞 Aconcagua 6/16/2023
至少这与 cppreference 的措辞相匹配(请注意,他们说的是捕获捕获列表)......
0赞 Eljay 6/16/2023
好像 lambda 是......struct accumulator_t { int sum{ 0 }; int operator()(int value) { sum += value; return sum; } };

答:

3赞 Jan Schultke 6/16/2023 #1

隐式期望(在注释中)已实现,但我不认识此捕获语法:在 lambda 声明范围内不存在,因此它无法捕获它。sum

sum不必如此。 不是我们正在捕获的实体的名称,它只是 capture 子句中的一个名称,它是 lambda 数据成员的别名,我们可以在调用运算符中使用。仅当没有初始值设定项时,捕获的实体的名称也可用。 此功能(带有初始值设定项的捕获子句)称为广义捕获sumsumsum

如果我们将上面的代码片段转换为“引擎盖下”发生的事情,则会更有意义:

int main() {
    // exposition-only, closure type is unnamed.
    struct __lambda {
        // exposition-only, data member is nameless.
        // lambda is mutable, so member is not const.
        int __sum;
        
        // return type will deduce to int.
        // mutable lambda means our call operator is not const-qualified.
        // call operator is automatically constexpr because it can be.
        constexpr auto operator()(int value) {
            __sum += value;
            return __sum;
        }
    };

    // We've used copy initialization in our capture, so copy initialization
    // must take place for the __sum member.
    // However, closure types are not aggregate types.
    auto accumulator = __lambda{.__sum = 0};
    accumulator(1);
    accumulator(2);
    return accumulator(-1);
}

您可以看到,在我们的 capture 子句中,仅仅意味着我们在闭包类型中有一个 type 的成员。[sum = 0]int

每个数据成员的类型是相应捕获实体的类型,除非该实体具有引用类型。

- 参见 cppreference

在这种情况下,捕获的实体是 的临时对象 ,它的类型是 ,所以我们的数据成员也是 类型 。0intint


Смотритетакже: 您在 cppinsights.io 上的代码