Search code examples
c++c++11lambdastdcapture

Why capture lambda does not working in c++?


I am playing with lambda expressions in C++, and I have tried a few things to see the outcome. I actually watched the video in CppCon Back to Basics: Lambdas from Scratch - Arthur O'Dwyer - CppCon 2019 @21:47 and started to play with lambdas.

As an example, I've tried this:

#include <iostream>
using namespace std;
int g = 10;//global var 'g'

//creating lambda
auto kitten = [=] () {return g+1;};
auto cat = [g=g] () {return g+1;};
// main
int main()
{
    g = 20;//modifying global variable 'g'
    cout<<"kitten: "<<kitten()<<"cat: "<<cat()<<endl;

    return 0;
}

Output of the above code is:

kitten: 21cat: 11

In the above example: [g=g] means capture a data member whose name is g and whose type is the same as the outer g, as if I had written auto g=g. It's a copy of g. Which makes sense when we think that (as if I had written in the form of auto g=g) so the result is 11 in our case, where modification of the global g is not reflected in our local g.

The result for the kitten is 21 because as far as I understand, capture everything i.e., capture all external variable by value.

Then, when it comes to this example by modifying the first lambda as follows:

auto kitten = [] () {int g  = g; return g+1;};

Where I declared local g and assigned value from global g, the output is:

kitten: 1cat: 11

But I was expecting the output as in the first example (21) because I am trying the create a local g and assigning its value from the global g, where it is already the modified value of 20.

Codes are compiled on https://techiedelight.com/compiler/ and godbolt.org with c++ (GCC 8.3.0) (with the latest compiler, [=] this is not allowed, but the results are the same).

At this moment, I am a little confused about the concept of capturing and/or lambda.


Solution

  • auto kitten = [=] () {return g+1;}
    

    This lambda doesn't capture anything at all. It's nearly the same as just

    int kitten() { return g+1; }
    

    Only local variables can be captured, and there are no local variables visible in the scope of the kitten definition. Note that [=] or [&] don't mean "capture everything", they mean "capture anything necessary", and a global variable is never necessary (or possible) to capture in a lambda, since the meaning of that variable name is always the same no matter when the lambda body is evaluated.


    auto cat = [g=g] () {return g+1;}
    

    Here's an init-capture, which is similar to creating a local variable and immediately capturing it. The g before the equal sign declares the init-capture, and the g after the equal sign specifies how to initialize it. Unlike most declarators (see below), the g variable created here is not in scope in its own initializer, so the g after the equal sign means the global variable ::g. So the code is similar to:

    auto make_cat()
    {
        int & g = ::g;
        return [g]() { return g+1; }
    }
    auto cat = make_cat();
    

    auto kitten = [] () {int g  = g; return g+1;}
    

    This code has a mistake not really related to lambdas. In the local variable definition int g = g;, the declared variable before the equal sign is in scope during the initializer after the equal sign. So g is initialized with its own indeterminate value. Adding one to that indeterminate value is undefined behavior, so the result is not predictable.