c++ lambda 表达式里 为什么值捕获的局部变量无法修改? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX    C++

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

  •  
  •   amiwrong123 2022-03-06 10:55:17 +08:00 3511 次点击
    这是一个创建于 1343 天前的主题,其中的信息可能已经有所发展或是发生改变。
    [&i] ( ) { std::cout << i; } // is equivalent to struct anonymous { int &m_i; anonymous(int &i) : m_i(i) {} iline 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()的匿名结构体的实例?如果不是的话,请帮忙举一个反例。

    12 条回复    2022-03-09 02:28:33 +08:00
    elfive
        1
    elfive  
       2022-03-06 11:05:54 +08:00 via iPhone
    [vl]这是按值捕获,要用引用捕获[&vl]
    codehz
        2
    codehz  
       2022-03-06 11:10:12 +08:00 via Android
    Lambda 确实就是匿名类型的实例化而已(没捕获的时候还带一个转换成函数指针的运算符重载)
    https://en.cppreference.com/w/cpp/language/lambda
    missdeer
        3
    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
        4
    amiwrong123  
    OP
       2022-03-06 11:10:57 +08:00
    @elfive #1
    是的,改成这样就可以编译通过。但我想知道,按值捕获的 lambda 表达式到底 实际上生成了什么样子的匿名结构体实例(然后才会造成,无法修改 按值捕获的变量)。
    victorbian
        5
    victorbian  
       2022-03-06 11:15:53 +08:00   1
    你想的没错,mutable 就是去掉 const ,而默认加 const 是为了让仿函数每次执行结果一致
    https://cppinsights.io/s/fc8369d5
    https://stackoverflow.com/a/5503690/8263383
    dangyuluo
        6
    dangyuluo  
       2022-03-06 12:54:40 +08:00
    Lambda 生成的类 operator()() 默认是 const 的,Google 搜索一下就有,实在是搜不到再发帖。

    看这里是怎么生成的

    https://cppinsights.io/s/8efa4ddf
    dangyuluo
        7
    dangyuluo  
       2022-03-06 12:55:55 +08:00   1
    文档里写: 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
        8
    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
        9
    amiwrong123  
    OP
       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
        10
    amiwrong123  
    OP
       2022-03-08 21:59:51 +08:00
    另外, 楼上的代码是在 cppinsights 的 C++14 上执行的哈
    codehz
        11
    codehz  
       2022-03-08 22:08:51 +08:00 via Android
    Cpp insight 也就图一乐,为了便于理解才这样生成)
    看这个去理解标准那无异于刻舟求剑(
    victorbian
        12
    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();`
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2782 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 07:34 PVG 15:34 LAX 23:34 JFK 02:34
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86