lambda's captured variable is reset

此生再无相见时 提交于 2020-02-05 02:51:11

问题


I'm trying to use lambdas 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?)


回答1:


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.




回答2:


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.




回答3:


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.



来源:https://stackoverflow.com/questions/48241935/lambdas-captured-variable-is-reset

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!