Use lambda through std::function reference, you cannot modify the value in lambda
Asked Answered
A

3

6

The lambda is used by std::function reference, but the value in the lambda cannot be changed. I don't know why? I've used references, but still can't achieve that.

#include <iostream>
#include <functional>

void myInvoke(const std::function<void()>& fn)
{
    fn();
}

int main()
{
    int i{ 0 };

    // Increments and prints its local copy of @i.
    auto count{ [i]() mutable {
      std::cout << ++i << '\n';
    } };

    myInvoke(count);
    myInvoke(count);
    myInvoke(count);

    return 0;
}

Actual results:

1
1
1

Expected results:

1
2
3

After I remove the & symbol, the running result has not changed. I think there should be a difference between the two, but I don't know what it is!

Axon answered 4/2, 2023 at 13:59 Comment(5)
Your std::function gets a copy of the lambda instance.Egest
And you don't capture i by reference, use auto count{ [&]() { std::cout << ++i << '\n'; } };Hockenberry
@PepijnKramer although it will generate the 1 2 3 output, it will be only via changing the i in main, and I don't think this is what the OP tried to achieve. In the begining I was going in that direction as well, but then thought the OP would like to use the i member of the lambda, without affecting i of main. My answer shows how to do it, as well as the answer by Matt.Signpost
@Axon - can you clarify if your intention was to modify the i member of the lambda only (without affecting i in main) ?Signpost
Yes, my purpose is to modify i in lambda, I don't care whether to modify i in main.Axon
S
6

You attempt myInvoke to accept the function fn by reference, but the count lambda that you pass is not a std::function.
Each time you call myInvoke with your lambda, it is converted to a std::function by means of creating a temporary object (a copy). That's what fn is referencing.
And so i will be modified on the copy and will not affect your count lambda.
This is also why accepting fn by value behaves similarly.

One way to handle it is to make myInvoke a function template where fn is a template argument (of type T &), so no conversion needs to be done.

Code example:

#include <iostream>
#include <functional>

template <typename T>
void myInvoke(T & fn)
{
    fn();
}

int main()
{
    int i{ 0 };

    // Increments and prints its local copy of @i.
    auto count = [i]() mutable {
      std::cout << ++i << '\n';
    };

    myInvoke(count);
    myInvoke(count);
    myInvoke(count);
}

Output:

1
2
3

An alternative way would be to make count of type std::function<void()> as suggested in the other answer by @Matt.
This will also prevent the creation of temporary copies per call, since the fn would be able to bind to it directly.

Note:
Another option that was mentioned is to change your lambda to capture i by reference instead of by value:

//------------vv------------
auto count = [&i]() { ... };

This will output 1 2 3 as you expect, but only because all function copies will actually modify the i on the stack of main.
I thought this is not what you wanted, but rather to increment only the i member of the lambda.

Signpost answered 4/2, 2023 at 14:9 Comment(2)
I think you made a very crucial point: lambdas are not std::function Because lambda is not std::function, when executing parameter passing (const std::function<void()> &fn = lambda), a std::function<void()> will be generated according to lambda to solve the type mismatch The problem Is my understanding above correct? Thank you for your answerAxon
@Axon Yes, this is correct.Signpost
R
3

Declare count as std::function<void()> and remove the const from myInvoke's parameter then it works as expected:

#include <functional>
#include <iostream>

void myInvoke(std::function<void()> &fn) {
  fn();
}

int main() {
  int i{ 0 };

  // Increments and prints its local copy of @i.
  std::function<void()> count{[i]() mutable {
    std::cout << ++i << '\n';
  }};

  myInvoke(count);
  myInvoke(count);
  myInvoke(count);

  return 0;
}

Output:

1
2
3

@wohlstad's answer gives a good explanation for why you need to declare count as std::function<void()> instead of auto to prevent a temporary object being made when calling myInvoke().

Btw if you replace [i] with [i = 0] then you don't even need to declare int i in main().

Rheotaxis answered 4/2, 2023 at 14:12 Comment(0)
C
2

Your lambda count captures i by copy. To capture i by reference you need

auto count{ [&i]() mutable {
  std::cout << ++i << '\n';
} };

Test it live on Coliru.

As noted in a comment, you do not need mutable in the lambda when capturing by reference.

Cliquish answered 4/2, 2023 at 14:2 Comment(2)
If you capture by reference the mutable is not necessary ;)Hockenberry
It will work by modifying the i in main. I believe the OP wanted to achieve this by modifying the i member of the lambda only. This is shown in mine and the other answer.Signpost

© 2022 - 2024 — McMap. All rights reserved.