c++ lambda 表达式里 为什么值捕获的局部变量无法修改?

2022-03-06 10:55:17 +08:00
 amiwrong123
[&i] ( ) { std::cout << i; }

// is equivalent to

struct anonymous
{
    int &m_i;
    anonymous(int &i) : m_i(i) {}
    inline auto operator()() const
    {
        std::cout << m_i;
    }
};

首先我知道,一个如上的 lambda 表达式,其实相当于生成了一个如上匿名 struct 的实例 a ,运行 lambda 表达式时,其实就相当于执行 a().

int main()
{
    int v1 = 42;
    auto f = [v1]() {return ++v1; };//值捕获
    v1 = 0;
    auto j = f(); //j 为 43

}

但是如上的代码却无法通过编译,除非你加上 mutable 。所以我是不是可以认为,如上的 lambda 表达式,是不是实际生成了如下的 struct ?

struct anonymous
{
    int m_i;
    anonymous(int i) : m_i(i) {}
    inline auto operator()() const//如果你加上 mutable ,这里的 const 才会去掉
    {
        m_i++;
    }
};

另外,c++ lambda 表达式是不是都可以认为 它的等价情况,就是生成了一个 重载了 operator()的匿名结构体的实例?如果不是的话,请帮忙举一个反例。

3026 次点击
所在节点    C++
12 条回复
elfive
2022-03-06 11:05:54 +08:00
[vl]这是按值捕获,要用引用捕获[&vl]
codehz
2022-03-06 11:10:12 +08:00
Lambda 确实就是匿名类型的实例化而已(没捕获的时候还带一个转换成函数指针的运算符重载)
https://en.cppreference.com/w/cpp/language/lambda
missdeer
2022-03-06 11:10:52 +08:00
这么理解没错,可以上 Compiler Explorer 验证一下,比如带 mutable 就是没 const: https://godbolt.org/z/87EaPznM8
不带 mutable 就是有 const: https://godbolt.org/z/Y51KdT74j
不过虽然现在的 gcc 和 msvc 确实都把 lambda 用仿函数实现,但这只是实现的方式,标准应该没说一定要这么做
amiwrong123
2022-03-06 11:10:57 +08:00
@elfive #1
是的,改成这样就可以编译通过。但我想知道,按值捕获的 lambda 表达式到底 实际上生成了什么样子的匿名结构体实例(然后才会造成,无法修改 按值捕获的变量)。
victorbian
2022-03-06 11:15:53 +08:00
你想的没错,mutable 就是去掉 const ,而默认加 const 是为了让仿函数每次执行结果一致
https://cppinsights.io/s/fc8369d5
https://stackoverflow.com/a/5503690/8263383
dangyuluo
2022-03-06 12:54:40 +08:00
Lambda 生成的类 operator()() 默认是 const 的,Google 搜索一下就有,实在是搜不到再发帖。

看这里是怎么生成的

https://cppinsights.io/s/8efa4ddf
dangyuluo
2022-03-06 12:55:55 +08:00
文档里写: https://en.cppreference.com/w/cpp/language/lambda

Unless the keyword mutable was used in the lambda-expression, the function-call operator or operator template is const-qualified and the objects that were captured by copy are non-modifiable from inside this operator()
dcsuibian
2022-03-06 15:17:46 +08:00
我的理解,这是实现机制上的问题。Java 里也有一样的要求来着。

lambda 里的 v1 并不是外面的 v1 。例如,原来的 v1 地址是 0x12345678 ,而 lambda 里的 v1 的地址是 0x23456789 ,它是把原来 0x12345678 里存的东西放到了 0x23456789 里面。所以实际上这俩不是一个东西。所以 v1 并没有跳脱它的作用域。

那为什么要默认给他加个 const ?我以 Java 打比方。

```java
public void test() {
int v1=5;
Runnable runnable=()->{
v1+=2;
};
runnable.run();
System.out.println(v1);
}
```

这个程序里面,创建了一个 lambda 并立即执行,我们会很自然地认为输出的 v1 是 7 ,但从实现的角度来说,由于 lambda 表达式里的 v1 并不是外面的那个 v1 ,仍然是 5 。
语言开发者为了避免出现这个奇怪的现象就禁止了在 lambda 里面修改 v1 ,所以我这个程序里 runnable 里的 v1+=2 这个部分会报错。
amiwrong123
2022-03-08 21:56:14 +08:00
@elfive #1
@codehz #2
@missdeer #3
@victorbian #5
@dangyuluo #6
@dcsuibian #8

各位大佬,问个问题。
https://cppinsights.io/s/69a816df
就是如上这个代码,为什么我看 main 函数里生成的 local class 里,有这么定义的 Member templates:
```
template<class ... type_parameter_0_0>
inline auto operator()(type_parameter_0_0... param) const
{
print(param...);
}
```
但实际上,local class 不能有 Member templates ( https://zh.cppreference.com/w/cpp/language/member_template 这里有说,你把 右边的代码放进 vs 也是编译不能通过)。
所以,为什么实际生成的代码 违反了 invalid declaration of member template in local class ?

另外,之所以还需要 void print() {}这个函数定义,是不是因为:最后一次调用 void print(const First& first, Rest &&... args)时,第二个参数 args 实际上已经是 void ,所以接着又会调用到 void print() {}里面?(不知道我这么解释对不对,所以不对,请帮忙用正确的术语描述一下😂)
amiwrong123
2022-03-08 21:59:51 +08:00
另外, 楼上的代码是在 cppinsights 的 C++14 上执行的哈
codehz
2022-03-08 22:08:51 +08:00
Cpp insight 也就图一乐,为了便于理解才这样生成)
看这个去理解标准那无异于刻舟求剑(
victorbian
2022-03-09 02:28:33 +08:00
@amiwrong123 C++14 允许 lambda 表达式( local class 的一种)带有 member templates ,参见 [cppreference]( https://en.cppreference.com/w/cpp/language/class#:~:text=Local%20classes%20other%20than%20closure%20types%20(since%20C%2B%2B14)%20cannot%20have%20member%20templates),估计正因为如此 C++14 中 lambda 表达式才开始支持 auto 形参。

关于 `void print(){}`,cppinsights 里已经很清楚了,最后一次调用 `void print<double>(const double & first)` 会调用 `print();`

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/838315

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX