Passing parameters to lambda in C++
Asked Answered
G

5

20

I seems to miss some point in lambda mechanism in C++. Here is the code:

std::vector<int> vec (5);

int init = 0;
std::generate(begin(vec), end(vec), [init]() mutable { return ++init; });

for (auto item : vec) {
    std::cout << item << " ";
}
std::cout << std::endl << init << std::endl;

If there is no mutable it wouldn't compile because I'm changing init in lambda.
Now, as I understand lambda is called for each vector's item with a new fresh copy of init which is 0. So, 1 must be returned every time. But the output of this piece of code is:
1 2 3 4 5
0

It looks like generate captures by copy init only once at the beginning of its execution. But why? Is it supposed to work like this?

Germanize answered 14/5, 2016 at 0:43 Comment(3)
That's exactly how its supposed to workAragonite
You'd probably get what you are describing if you used a for loop and in the body of the loop you did something like vec[i] = ([init] () mutable {return ++init;})();, because this creates one lambda function every iteration.Eyla
@Bakuriu. That was my point of confusion - I thought that for loop and generate(begin, end, lambda) should do the same thing.Germanize
M
32

Now, as I understand lambda is called for each vector's item with a new fresh copy of init which is 0.

That is not correct. A lambda is just a another way to make a class and provide an operator() for it. The [] part of the lambda describes the member variables and whether they are captured by reference or value. The () part of the lambda is the parameter list for the operator() and the {} part is the body of that function. The mutable part tells the compiler to make the operator() non const as it is const by default.

So

[init]() mutable { return ++init; }

Becomes

struct compiler_generated_name
{
    int init; // we captured by value

    auto operator()() // since we used mutable this is non const
    {
        return ++init;
    }
};

I used a struct here for brevity of typing but a lambda is specified as a class type so class could be used.

This means the init is the same init from the last iteration as you only ever capture once. This is important to remember as

auto generate_lambda()
{
    int foo = 0;
    return [&foo](){ return ++foo; };
}

Will leave you with a dangling reference to foo when the function returns and using it is undefined behavior.

Multi answered 14/5, 2016 at 0:48 Comment(1)
This is the best/simplest explanation that I've seen for implementing lambda expressions. Thanks!Laina
L
10

The lambda is a compiler-generated struct equivalent to:

struct lambda
{
    int init = 0; // captured value

    auto operator()() // non-const, due to `mutable`
    {
        return ++init;
    }
};

Therefore, init is captured and copied inside the lambda just once - calling the lambda multiple times will not capture init again.

Liscomb answered 14/5, 2016 at 0:46 Comment(0)
K
5

You are coping and seeing the initial value of init -- what you you probably want to do acording to what you expect is to capture init by reference.....

std::vector<int> vec (5);

int init = 0;
std::generate(begin(vec), end(vec), [&init]() mutable { return ++init; });

for (auto item : vec) {
    std::cout << item << " ";
}
std::cout << std::endl << init << std::endl;
Kernan answered 14/5, 2016 at 0:47 Comment(8)
I used it many times with [&param] and know how it works. This is just an example code which I run after read about mutable applied to lambdasGermanize
How does this explain why an increasing list of values was generated and how does capturing by reference fix that?Multi
@NathanOliver: Is it a test for me?))Germanize
@Germanize No. I am asking Soren as I don not understand what question he is trying to answer.Multi
I was explaining why the last line of output was 0 -- somehow thought that was the question.Kernan
@Kernan Ah okay. I didn't see that part.Multi
It's OK, my question wasn't "how to make all the values 1" or "how to make them sequential". But rather "why they are sequential while I expect them to be equal".Germanize
Note: The mutable keyword is not needed when the capture is by reference rather than by value.Oldfashioned
C
3

Your error is here "Now, as I understand lambda is called for each vector's item with a new fresh copy of init which is 0" (my italics). No; as you can see the lambda is completely separate, and therefore ignorant, of the vector code. The initialisation of item takes place each time the lambda form itself is evaluated (as opposed to each time the resulting value is called); here this means each time the function generate is called: just once.

Cates answered 14/5, 2016 at 7:14 Comment(0)
G
1
int x = ([](int j)->int {return j; }, 3);
Grendel answered 26/12, 2022 at 3:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.