为什么 C++11 的 lambda 需要“可变”默认情况下按值捕获的关键字?

发布于 2024-10-28 04:07:45 字数 528 浏览 1 评论 0原文

简短示例:

#include <iostream>

int main()
{
    int n;
    [&](){n = 10;}();             // OK
    [=]() mutable {n = 20;}();    // OK
    // [=](){n = 10;}();          // Error: a by-value capture cannot be modified in a non-mutable lambda
    std::cout << n << "\n";       // "10"
}

问题:为什么我们需要 mutable 关键字?它与传递给命名函数的传统参数有很大不同。背后的原理是什么?

我的印象是,按值捕获的全部要点是允许用户更改临时值 - 否则我几乎总是使用按引用捕获更好,不是吗?

有什么启示吗?

(顺便说一下,我正在使用 MSVC2010。据我所知,这应该是标准的)

Short example:

#include <iostream>

int main()
{
    int n;
    [&](){n = 10;}();             // OK
    [=]() mutable {n = 20;}();    // OK
    // [=](){n = 10;}();          // Error: a by-value capture cannot be modified in a non-mutable lambda
    std::cout << n << "\n";       // "10"
}

The question: Why do we need the mutable keyword? It's quite different from traditional parameter passing to named functions. What's the rationale behind?

I was under the impression that the whole point of capture-by-value is to allow the user to change the temporary -- otherwise I'm almost always better off using capture-by-reference, aren't I?

Any enlightenments?

(I'm using MSVC2010 by the way. AFAIK this should be standard)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

为人所爱 2024-11-04 04:07:45

它需要可变的,因为默认情况下,函数对象每次调用时都应该产生相同的结果。这实际上是面向对象函数和使用全局变量的函数之间的区别。

It requires mutable because by default, a function object should produce the same result every time it's called. This is the difference between an object orientated function and a function using a global variable, effectively.

忆离笙 2024-11-04 04:07:45

您的代码几乎与此相同:

#include <iostream>

class unnamed1
{
    int& n;
public:
    unnamed1(int& N) : n(N) {}

    /* OK. Your this is const but you don't modify the "n" reference,
    but the value pointed by it. You wouldn't be able to modify a reference
    anyway even if your operator() was mutable. When you assign a reference
    it will always point to the same var.
    */
    void operator()() const {n = 10;}
};

class unnamed2
{
    int n;
public:
    unnamed2(int N) : n(N) {}

    /* OK. Your this pointer is not const (since your operator() is "mutable" instead of const).
    So you can modify the "n" member. */
    void operator()() {n = 20;}
};

class unnamed3
{
    int n;
public:
    unnamed3(int N) : n(N) {}

    /* BAD. Your this is const so you can't modify the "n" member. */
    void operator()() const {n = 10;}
};

int main()
{
    int n;
    unnamed1 u1(n); u1();    // OK
    unnamed2 u2(n); u2();    // OK
    //unnamed3 u3(n); u3();  // Error
    std::cout << n << "\n";  // "10"
}

因此,您可以将 lambda 视为使用 operator() 生成一个类,该类默认为 const,除非您说它是可变的。

您还可以将 [] 内捕获的所有变量(显式或隐式)视为该类的成员:[=] 对象的副本或 [&] 对象的引用。当您声明 lambda 时,它们会被初始化,就像有一个隐藏的构造函数一样。

Your code is almost equivalent to this:

#include <iostream>

class unnamed1
{
    int& n;
public:
    unnamed1(int& N) : n(N) {}

    /* OK. Your this is const but you don't modify the "n" reference,
    but the value pointed by it. You wouldn't be able to modify a reference
    anyway even if your operator() was mutable. When you assign a reference
    it will always point to the same var.
    */
    void operator()() const {n = 10;}
};

class unnamed2
{
    int n;
public:
    unnamed2(int N) : n(N) {}

    /* OK. Your this pointer is not const (since your operator() is "mutable" instead of const).
    So you can modify the "n" member. */
    void operator()() {n = 20;}
};

class unnamed3
{
    int n;
public:
    unnamed3(int N) : n(N) {}

    /* BAD. Your this is const so you can't modify the "n" member. */
    void operator()() const {n = 10;}
};

int main()
{
    int n;
    unnamed1 u1(n); u1();    // OK
    unnamed2 u2(n); u2();    // OK
    //unnamed3 u3(n); u3();  // Error
    std::cout << n << "\n";  // "10"
}

So you could think of lambdas as generating a class with operator() that defaults to const unless you say that it is mutable.

You can also think of all the variables captured inside [] (explicitly or implicitly) as members of that class: copies of the objects for [=] or references to the objects for [&]. They are initialized when you declare your lambda as if there was a hidden constructor.

东走西顾 2024-11-04 04:07:45

你必须明白捕获意味着什么!它是捕获而不是参数传递!让我们看一些代码示例:

int main()
{
    using namespace std;
    int x = 5;
    int y;
    auto lamb = [x]() {return x + 5; };

    y= lamb();
    cout << y<<","<< x << endl; //outputs 10,5
    x = 20;
    y = lamb();
    cout << y << "," << x << endl; //output 10,20

}

正如您所见,即使 x 已更改为 20,lambda 仍然返回 10(x 仍然是lambda 内的 5
更改 lambda 内部的 x 意味着在每次调用时更改 lambda 本身(lambda 在每次调用时都会发生变化)。为了保证正确性,标准引入了 mutable 关键字。通过将 lambda 指定为可变,您可以说每次调用 lambda 都可能导致 lambda 本身发生变化。让我们看另一个例子:

int main()
{
    using namespace std;
    int x = 5;
    int y;
    auto lamb = [x]() mutable {return x++ + 5; };

    y= lamb();
    cout << y<<","<< x << endl; //outputs 10,5
    x = 20;
    y = lamb();
    cout << y << "," << x << endl; //outputs 11,20

}

上面的例子表明,通过使 lambda 可变,更改 lambda 内部的 x 会在每次调用时使用新的 x 值“变异” lambda与主函数中x的实际值无关

You have to understand what capture means! it's capturing not argument passing! let's look at some code samples:

int main()
{
    using namespace std;
    int x = 5;
    int y;
    auto lamb = [x]() {return x + 5; };

    y= lamb();
    cout << y<<","<< x << endl; //outputs 10,5
    x = 20;
    y = lamb();
    cout << y << "," << x << endl; //output 10,20

}

As you can see even though x has been changed to 20 the lambda is still returning 10 ( x is still 5 inside the lambda)
Changing x inside the lambda means changing the lambda itself at each call (the lambda is mutating at each call). To enforce correctness the standard introduced the mutable keyword. By specifying a lambda as mutable you are saying that each call to the lambda could cause a change in the lambda itself. Let see another example:

int main()
{
    using namespace std;
    int x = 5;
    int y;
    auto lamb = [x]() mutable {return x++ + 5; };

    y= lamb();
    cout << y<<","<< x << endl; //outputs 10,5
    x = 20;
    y = lamb();
    cout << y << "," << x << endl; //outputs 11,20

}

The above example shows that by making the lambda mutable, changing x inside the lambda "mutates" the lambda at each call with a new value of x that has no thing to do with the actual value of x in the main function

任谁 2024-11-04 04:07:45

我的印象是,按值捕获的全部意义在于允许用户更改临时值 - 否则我几乎总是使用按引用捕获更好,不是吗?< /p>

问题是,“几乎”了吗?一个常见的用例似乎是返回或传递 lambda:

void registerCallback(std::function<void()> f) { /* ... */ }

void doSomething() {
  std::string name = receiveName();
  registerCallback([name]{ /* do something with name */ });
}

我认为 mutable 不是“几乎”的情况。我认为“按值捕获”就像“允许我在捕获的实体死亡后使用它的值”,而不是“允许我更改它的副本”。但也许这还可以争论。

I was under the impression that the whole point of capture-by-value is to allow the user to change the temporary -- otherwise I'm almost always better off using capture-by-reference, aren't I?

The question is, is it "almost"? A frequent use-case appears to be to return or pass lambdas:

void registerCallback(std::function<void()> f) { /* ... */ }

void doSomething() {
  std::string name = receiveName();
  registerCallback([name]{ /* do something with name */ });
}

I think that mutable isn't a case of "almost". I consider "capture-by-value" like "allow me to use its value after the captured entity dies" rather than "allow me to change a copy of it". But perhaps this can be argued.

人疚 2024-11-04 04:07:45

FWIW,C++ 标准化委员会的知名成员 Herb Sutter 在 Lambda 正确性和可用性问题

考虑这个稻草人示例,其中程序员通过以下方式捕获局部变量
值并尝试修改
捕获的值(这是 lambda 对象的成员变量):

int val = 0;
auto x = [=](item e) // 看ma,[=]表示显式复制
            { 使用(e,++val); }; // 错误:计数是常量,需要“可变”
auto y = [val](item e) // 该死,我真的不能说得更明确了
            { 使用(e,++val); }; // 同样的错误:计数是常量,需要“可变”

添加此功能似乎是出于对用户的担忧
可能没有意识到他得到了一份副本,特别是自从 lambdas
是可复制的,他可能会更改不同的 lambda 副本。

他的论文是关于为什么应该在 C++14 中更改这一点。它很短,写得很好,如果您想了解“[委员会成员]对此特定功能的想法”,值得一读。

FWIW, Herb Sutter, a well-known member of the C++ standardization committee, provides a different answer to that question in Lambda Correctness and Usability Issues:

Consider this straw man example, where the programmer captures a local variable by
value and tries to modify the
captured value (which is a member variable of the lambda object):

int val = 0;
auto x = [=](item e)            // look ma, [=] means explicit copy
            { use(e,++val); };  // error: count is const, need ‘mutable’
auto y = [val](item e)          // darnit, I really can’t get more explicit
            { use(e,++val); };  // same error: count is const, need ‘mutable’

This feature appears to have been added out of a concern that the user
might not realize he got a copy, and in particular that since lambdas
are copyable he might be changing a different lambda’s copy.

His paper is about why this should be changed in C++14. It is short, well written, worth reading if you want to know "what's on [committee member] minds" with regards to this particular feature.

辞取 2024-11-04 04:07:45

您需要考虑 Lambda 函数的闭包类型是什么。每次声明 Lambda 表达式时,编译器都会创建一个闭包类型,它无非是一个带有属性的未命名类声明(声明 Lambda 表达式的环境)和函数调用: :operator() 已实现。当您使用按值复制捕获变量时,编译器将在闭包类型中创建一个新的const属性,因此您无法在 Lambda 表达式内更改它因为它是一个“只读”属性,这就是他们称之为“闭包”的原因,因为在某种程度上,您通过将变量从上层作用域复制到 Lambda 来关闭 Lambda 表达式范围。当您使用关键字mutable时,捕获的实体将成为闭包类型的非常量属性。这就是导致通过值捕获的可变变量中所做的更改不会传播到上层作用域,而是保留在有状态 Lambda 内部的原因。
始终尝试想象 Lambda 表达式的最终闭包类型,这对我帮助很大,我希望它也能对您有所帮助。

You need to think what is the closure type of your Lambda function. Every time you declare a Lambda expression, the compiler creates a closure type, which is nothing less than an unnamed class declaration with attributes (environment where the Lambda expression where declared) and the function call ::operator() implemented. When you capture a variable using copy-by-value, the compiler will create a new const attribute in the closure type, so you can't change it inside the Lambda expression because it is a "read-only" attribute, that's the reason they call it a "closure", because in some way, you are closing your Lambda expression by copying the variables from upper scope into the Lambda scope. When you use the keyword mutable, the captured entity will became a non-const attribute of your closure type. This is what causes the changes done in the mutable variable captured by value, to not be propagated to upper scope, but keep inside the stateful Lambda.
Always try to imagine the resulting closure type of your Lambda expression, that helped me a lot, and I hope it can help you too.

谁对谁错谁最难过 2024-11-04 04:07:45

请参阅此草案,第 5.1.2 节 [expr.prim .lambda],第 5 款:

lambda 表达式的闭包类型有一个公共内联函数调用运算符 (13.5.4),其参数
和返回类型由 lambda 表达式的参数声明子句和尾随返回来描述
分别键入。 当且仅当 lambda 表达式的
参数声明子句后面不跟可变的。

编辑 litb 的评论:
也许他们想到了按值捕获,以便变量的外部更改不会反映在 lambda 内部?参考文献是双向的,这就是我的解释。不过不知道好不好用。

编辑 kizzx2 的评论:
lambda 最常被用作算法的函子。默认的 const 性允许它在常量环境中使用,就像可以在其中使用普通的 const 限定函数一样,但非 const 函数可以在常量环境中使用。 - 有资格的人不能。也许他们只是想让这些案例变得更直观,因为他们知道他们脑子里在想什么。 :)

See this draft, under 5.1.2 [expr.prim.lambda], subclause 5:

The closure type for a lambda-expression has a public inline function call operator (13.5.4) whose parameters
and return type are described by the lambda-expression’s parameter-declaration-clause and trailingreturn-
type respectively. This function call operator is declared const (9.3.1) if and only if the lambdaexpression’s
parameter-declaration-clause is not followed by mutable.

Edit on litb's comment:
Maybe they thought of capture-by-value so that outside changes to the variables aren't reflected inside the lambda? References work both ways, so that's my explanation. Don't know if it's any good though.

Edit on kizzx2's comment:
The most times when a lambda is to be used is as a functor for algorithms. The default constness lets it be used in a constant environment, just like normal const-qualified functions can be used there, but non-const-qualified ones can't. Maybe they just thought to make it more intuitive for those cases, who know what goes on in their mind. :)

第七度阳光i 2024-11-04 04:07:45

我的印象是
按价值捕获的全部要点是
允许用户更改临时的
-- 否则我几乎总是使用引用捕获会更好,不是吗
我?

n 不是临时的。 n 是您使用 lambda 表达式创建的 lambda 函数对象的成员。默认期望是调用 lambda 不会修改其状态,因此它是 const 以防止您意外修改 n

I was under the impression that the
whole point of capture-by-value is to
allow the user to change the temporary
-- otherwise I'm almost always better off using capture-by-reference, aren't
I?

n is not a temporary. n is a member of the lambda-function-object that you create with the lambda expression. The default expectation is that calling your lambda does not modify its state, therefore it is const to prevent you from accidentally modifying n.

扬花落满肩 2024-11-04 04:07:45

如果您检查 lambda 的 3 个不同用例,您可能会发现其中的差异:

  • 按值捕获参数
  • 使用“mutable”关键字按值捕获参数
  • 通过引用捕获参数

情况 1:
当您按值捕获参数时,会发生一些事情:

  • 不允许您修改 lambda 内的参数
  • 每当 lambda 被调用时,参数的值保持不变
    调用,无论调用 lambda 时的参数值是什么。

例如:

{
    int x = 100;
    auto lambda1 = [x](){
      // x += 2;  // compile time error. not allowed
                  // to modify an argument that is captured by value
      return x * 2;
    };
    cout << lambda1() << endl;  // 100 * 2 = 200
    cout << "x: " << x << endl; // 100

    x = 300;
    cout << lambda1() << endl;   // in the lambda, x remain 100. 100 * 2 = 200
    cout << "x: " << x << endl;  // 300

}

Output: 
200
x: 100
200
x: 300

情况 2:
在这里,当您按值捕获参数并使用“mutable”关键字时,与第一种情况类似,您将创建该参数的“副本”。这个“副本”存在于 lambda 的“世界”中,但现在,您实际上可以在 lambda 世界中修改参数,因此它的值被更改并保存,并且可以在以后的调用中引用拉姆达。同样,论证的外部“生活”可能完全不同(价值方面):

{
    int x = 100;
    auto lambda2 = [x]() mutable {
      x += 2;  // when capture by value, modify the argument is
               // allowed when mutable is used.
      return x;
    };
    cout << lambda2() << endl;  // 100 + 2 = 102
    cout << "x: " << x << endl; // in the outside world - x remains 100
    x = 200;
    cout << lambda2() << endl;  // 104, as the 102 is saved in the lambda world.
    cout << "x: " << x << endl; // 200
}

Output:
102
x: 100
104
x: 200

情况 3:
这是最简单的情况,因为 x 不再有 2 条生命。现在 x 只有一个值,并且在外部世界和 lambda 世界之间共享。

{
    int x = 100;
    auto lambda3 = [&x]() mutable {
        x += 10;  // modify the argument, is allowed when mutable is used.
        return x;
    };
    cout << lambda3() << endl;  // 110
    cout << "x: " << x << endl; // 110
    x = 400;
    cout << lambda3() << endl;  // 410.
    cout << "x: " << x << endl; // 410
}

Output: 
110
x: 110
410
x: 410

You might see the difference, if you check 3 different use cases of lambda:

  • Capturing an argument by value
  • Capturing an argument by value with 'mutable' keyword
  • Capturing an argument by reference

case 1:
When you capture an argument by value, a few things happen:

  • You are not allowed to modify the argument inside the lambda
  • The value of the argument remains the same, whenever the lambda is
    called, not matter what will be the argument value at the time the lambda is called.

so for example:

{
    int x = 100;
    auto lambda1 = [x](){
      // x += 2;  // compile time error. not allowed
                  // to modify an argument that is captured by value
      return x * 2;
    };
    cout << lambda1() << endl;  // 100 * 2 = 200
    cout << "x: " << x << endl; // 100

    x = 300;
    cout << lambda1() << endl;   // in the lambda, x remain 100. 100 * 2 = 200
    cout << "x: " << x << endl;  // 300

}

Output: 
200
x: 100
200
x: 300

case 2:
Here, when you capture an argument by value and use the 'mutable' keyword, similar to the first case, you create a "copy" of this argument. This "copy" lives in the "world" of the lambda, but now, you can actually modify the argument within the lambda-world, so its value is changed, and saved and it can be referred to, in the future calls of this lambda. Again, the outside "life" of the argument might be totally different (value wise):

{
    int x = 100;
    auto lambda2 = [x]() mutable {
      x += 2;  // when capture by value, modify the argument is
               // allowed when mutable is used.
      return x;
    };
    cout << lambda2() << endl;  // 100 + 2 = 102
    cout << "x: " << x << endl; // in the outside world - x remains 100
    x = 200;
    cout << lambda2() << endl;  // 104, as the 102 is saved in the lambda world.
    cout << "x: " << x << endl; // 200
}

Output:
102
x: 100
104
x: 200

case 3:
This is the easiest case, as no more 2 lives of x. Now there is only one value for x and it's shared between the outside world and the lambda world.

{
    int x = 100;
    auto lambda3 = [&x]() mutable {
        x += 10;  // modify the argument, is allowed when mutable is used.
        return x;
    };
    cout << lambda3() << endl;  // 110
    cout << "x: " << x << endl; // 110
    x = 400;
    cout << lambda3() << endl;  // 410.
    cout << "x: " << x << endl; // 410
}

Output: 
110
x: 110
410
x: 410
亚希 2024-11-04 04:07:45

为了扩展 Puppy 的答案,lambda 函数旨在成为纯函数。这意味着给定唯一输入集的每个调用始终返回相同的输出。让我们将输入定义为调用 lambda 时所有参数加上所有捕获的变量的集合。

在纯函数中,输出仅取决于输入,而不取决于某些内部状态。因此,任何 lambda 函数(如果是纯函数)都不需要更改其状态,因此是不可变的。

当 lambda 通过引用捕获时,在捕获的变量上写入是对纯函数概念的一种压力,因为纯函数应该做的就是返回一个输出,尽管 lambda 不一定会发生变化,因为写入发生在外部变量上。即使在这种情况下,正确的用法也意味着如果再次使用相同的输入调用 lambda,则每次的输出都将相同,尽管对 by-ref 变量有这些副作用。此类副作用只是返回一些额外输入(例如更新计数器)的方法,并且可以重新表述为纯函数,例如返回元组而不是单个值。

To extend Puppy's answer, lambda functions are intended to be pure functions. That means every call given a unique input set always returns the same output. Let's define input as the set of all arguments plus all captured variables when the lambda is called.

In pure functions output solely depends on input and not on some internal state. Therefore any lambda function, if pure, does not need to change its state and is therefore immutable.

When a lambda captures by reference, writing on captured variables is a strain on the concept of pure function, because all a pure function should do is return an output, though the lambda does not certainly mutate because the writing happens to external variables. Even in this case a correct usage implies that if the lambda is called with the same input again, the output will be the same everytime, despite these side effects on by-ref variables. Such side effects are just ways to return some additional input (e.g. update a counter) and could be reformulated into a pure function, for example returning a tuple instead of a single value.

眼中杀气 2024-11-04 04:07:45

我也想知道这个问题,为什么 [=] 需要显式 mutable 的最简单解释是在这个例子中:

int main()
{
    int x {1};
    auto lbd = [=]() mutable { return x += 5; };
    printf("call1:%d\n", lbd());
    printf("call2:%d\n", lbd());
    return 0;
}

输出:

call1:6
call2:11

用文字:

您可以看到 x 值在第二次调用时不同(call1 为 1,call2 为 6)。

  1. lambda 对象按值保存捕获的变量(有自己的
    复制)(如果是 [=])。
  2. lambda 可以被调用多次。

在一般情况下,我们必须具有相同的捕获变量值,才能根据已知的捕获值具有相同的 lambda 可预测行为,而不是在 lambda 工作期间更新。这就是为什么默认行为采用 const(以预测 lambda 对象成员的更改),并且当用户意识到后果时,他会通过 mutable 自行承担此责任。

与按值捕获相同。以我为例:

auto lbd = [x]() mutable { return x += 5; };

I also was wondering about it and the simplest explanation why [=] requires explicit mutable is in this example:

int main()
{
    int x {1};
    auto lbd = [=]() mutable { return x += 5; };
    printf("call1:%d\n", lbd());
    printf("call2:%d\n", lbd());
    return 0;
}

Output:

call1:6
call2:11

By words:

You can see that the x value is different at the second call (1 for the call1 and 6 for the call2).

  1. A lambda object keeps a captured variable by value (has its own
    copy) in case of [=].
  2. The lambda can be called several times.

And in general case we have to have the same value of the captured variable to have the same predictable behavior of the lambda based on the known captured value, not updated during the lambda work. That's why the default behavior assumed const (to predict changes of the lambda object members) and when a user is aware of consequences he takes this responsibility on himself with mutable.

Same with capturing by value. For my example:

auto lbd = [x]() mutable { return x += 5; };
¢蛋碎的人ぎ生 2024-11-04 04:07:45

现在有一项提案可以减轻 lambda 声明中对 mutable 的需求:n3424

There is now a proposal to alleviate the need for mutable in lambda declarations: n3424

空‖城人不在 2024-11-04 04:07:45

这实际上将两个问题混为一谈,将它们分开可以更容易地提供答案:

  1. 为什么按值捕获修改捕获变量的 lambda 格式不正确?
  2. 为什么默认情况下 lambda 捕获的值是 const

问题#1 有一个简单的事实答案:默认情况下,lambda 捕获的值是 const。在这些情况下:

int n = 0;
[=](){n = 10;}();
[n](){n = 10;}();
[val = n](){val = 10;}();

nval 基本上是 const int 类型的 lambda 的“成员”。您无法修改类型为 const int 的类或结构的成员,因为它是 const。

问题#2 是一个关于语言设计的问题,答案可能很复杂或固执己见。这里的其他答案已经给出了许多可能的答案,但其中许多都没有回答问题#1,所以我认为它们对于只对 lambda 捕获如何工作而不讨论语言设计原理和推理感兴趣的访问者没有帮助。

This really mixes up two questions into one, and separating them makes it easier to provide answers:

  1. Why is a lambda capturing by value that modifies a captured variable ill-formed?
  2. Why are values captured by lambdas const by default?

Question #1 has a simple factual answer: Values captured by lambdas are const by default. In these cases:

int n = 0;
[=](){n = 10;}();
[n](){n = 10;}();
[val = n](){val = 10;}();

n or val are basically "members" of the lambdas of type const int. You cannot modify a member of a class or struct that has type const int, because it is const.

Question #2 is a question about language design, and the answer can be complicated or opinionated. Many possible answers have been given in the other answers here already, but many of them do not answer question #1, so I think they are not helpful for visitors who are only interested in how lambda capture works, not discuss language design principles and reasoning.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文