2

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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
asevindik
  • 131
  • 9
  • `[g=g]` is the same as `[auto g=g]` and is what you are doing. `=` means copy in a lambda capture, `&` means reference. – NathanOliver Aug 02 '21 at 12:21
  • @NathanOliver Except that `[auto g=g]` isn't a real syntax. (If one really needs to force a type on an init-capture, there's `[v=static_cast(e)]`.) – aschepler Aug 02 '21 at 12:52
  • 1
    @Yakk - Adam Nevraumont It can be compiled from here:https://techiedelight.com/compiler/ with gcc 8.3.0 as already stated in the post! Please read carefully and try out there. – asevindik Aug 02 '21 at 13:56
  • unless capture list is empty and no external non-constexpr symbols are referenced in lambda expression's body, this lambda does perform capturing. One reliable sign of capture-less lambda expression is that it can bound to a pointer to function. – Swift - Friday Pie Aug 02 '21 at 18:17

2 Answers2

10
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.

aschepler
  • 70,891
  • 9
  • 107
  • 161
7
auto kitten = [] () {int g  = g; return g+1;};

You aren't using a global at all here. You're using the local g to initialise the local g. The behaviour of the program is undefined.

why int g = g; trying to initialize local g by itself,

Because the initialiser is after the point where the local g has been declared.

shouldn't that compiler initialize with the global g?

No.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 5
    Perhaps throw in a suggestion how to address the global: `auto kitten = [] () {int g = ::g; return g+1;};` – Ted Lyngmo Aug 02 '21 at 12:34
  • @eerorika Why is that so? We can create a local variable with the same name inside another function and in the main we can access the global `g` again. My point is why int g = g; trying to initialize local `g` by itself, shouldn't that compiler initialize with the global g? from right to left? e.g: calling function `foo()` and inside foo() int g= 33 will be local, in the main after the calling foo(), output of the global `g` is still remaining same? There is no problem accessing global var. inside another function but why is this init. is problematic? – asevindik Aug 02 '21 at 12:36
  • @Ted Lyngmo, Yes, you are right, when we use `::` operator, it is working as expected! But I thought, by default, compiler should do this. As stated previous comment above, from right to left... – asevindik Aug 02 '21 at 12:42
  • See also https://stackoverflow.com/questions/9820027/using-newly-declared-variable-in-initialization-int-x-x1 – aschepler Aug 02 '21 at 12:46