amiwrong123
V2EX  ›  C++

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

  •  
  •   amiwrong123 · Mar 6, 2022 · 3899 views
    This topic created in 1542 days ago, the information mentioned may be changed or developed.
    [&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()的匿名结构体的实例?如果不是的话,请帮忙举一个反例。

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

    看这里是怎么生成的

    https://cppinsights.io/s/8efa4ddf
    dangyuluo
        7
    dangyuluo  
       Mar 6, 2022   ❤️ 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  
       Mar 6, 2022
    我的理解,这是实现机制上的问题。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
       Mar 8, 2022
    @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
       Mar 8, 2022
    另外, 楼上的代码是在 cppinsights 的 C++14 上执行的哈
    codehz
        11
    codehz  
       Mar 8, 2022 via Android
    Cpp insight 也就图一乐,为了便于理解才这样生成)
    看这个去理解标准那无异于刻舟求剑(
    victorbian
        12
    victorbian  
       Mar 9, 2022
    @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();`
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5243 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 72ms · UTC 09:04 · PVG 17:04 · LAX 02:04 · JFK 05:04
    ♥ Do have faith in what you're doing.