Reputation: 6026
I'm trying to use lambda
s inside a project but I think I'm missing something about the closure's scope. I tested this piece of code which in some way is a simplification of my problem.
#include <iostream>
#include <functional>
using namespace std;
void tester_wrapper(std::function<int(void)> cb, int counter){
if (counter == 10)
return;
else{
cout << cb() << endl;
tester_wrapper(cb, counter + 1);
}
}
void tester(std::function<int(void)> cb){
tester_wrapper(cb, 0);
}
int main()
{
auto getNum = ([](int starter) {
return [starter]() mutable {
return ++starter;
};
})(1);
tester(getNum);
tester(getNum);
}
After the first call to tester
the captured variable starter
is reset so that the same output is printed twice.
What should I do in order to avoid this behaviour of the inner counter(starter
) of the lambda? Essentially the second call to tester
has to print 10 numbers starting from 12 instead of 2.
EDIT
Thank you for the answers. I have not considered that I was passing a copy to tester_wrapper
, so I've found this solution:
#include <iostream>
#include <functional>
using namespace std;
std::function<int(void)> mylambda(int starter){
return [starter]() mutable {
return ++starter;
};
}
void tester_wrapper(const std::function<int(void)>& cb, int counter){
if (counter == 10)
return;
else{
cout << cb() << endl;
tester_wrapper(cb, counter + 1);
}
}
void tester(const std::function<int(void)>& cb){
tester_wrapper(cb, 0);
}
int main()
{
/*auto getNum = ([](int starter) {
return [starter]() mutable {
return ++starter;
};
})(1);*/
auto getNum = mylambda(1);
tester(getNum);
tester(getNum);
}
However, now I can't understand why the old getNum
print the same output while it's different using an "external" function, which is mylambda
.
(Am I supposed to post a new question for this?)
Upvotes: 2
Views: 500
Reputation: 170065
The variable isn't reset, it's a different copy of the variable. In fact, there are a bunch copies. The first is in the state of the lambda you create. The second is created when the first std::function
is constructed. You must remember that it copies the callable it receives into itself. So each invocation of tester
starts a chain of copies. One way to get around it, is to pass the lambda inside a std::reference_wrapper
.
tester(std::ref(getNum));
tester(std::ref(getNum));
The reference wrapper will be copied, but all copies will refer to the same lambda object, getNum
.
Now, assuming you intend to create many different objects like getNum
, a std::function
and the type erasure it provides are a reasonable course of action to avoid possible code bloat. The thing to remember is to not create superfluous copies. So tester
accepting by value is legitimate, but tester_wrapper
should accept by reference instead. That way, you'll only pay for the type erasure in the one place you need it, at the API boundary.
Upvotes: 3
Reputation: 17483
One possible solution is to create a separate variable starter
and then capture it by reference so the lambda changes the actual variable:
auto starter = 0;
auto getNum = [&starter]() {
return ++starter;
};
Then you just call:
tester(getNum);
tester(getNum);
The output will be numbers from 1
to 20
.
Upvotes: 3
Reputation: 361342
The argument getNum
you're passing to tester
is being copied by std::function<int(void)>
. That is, std::function<int(void)>
does not store the original getNum
, instead it stores a copy.
@StoryTeller and @Edgar already suggested two solutions. Here is third one:
template<typename Callback>
void tester_wrapper(Callback && cb, int counter){
if (counter == 10)
return;
else{
cout << cb() << endl;
tester_wrapper(cb, counter + 1);
}
}
template<typename Callback>
void tester(Callback && cb){
tester_wrapper(std::forward<Callback>(cb), 0);
}
Now there is no copy, as both functions accept the argument by reference.
By the way, this code is likely to be faster than the other two, as other two continue to use std:function
which has one virtual call or equivalent to invoke the stored callable.
Hope that helps.
Upvotes: 2