Share variable between two lambdas

1.2k views Asked by At

I want to be able to share a variable in the containing scope between two lambda functions. I have the following:

void holdAdd(const Rect& rectangle, Hold anonymousHeld, Hold anonymousFinish) {
    std::map<int,bool> identifierCollection;

    HoldFinish holdFinish = [=](const int& identifier) mutable {
        if (identifierCollection.count(identifier) == 0) return;

        identifierCollection.erase(identifier);
        anonymousFinish();
    };

    holdCollisionCollection.push_back([=](const int& identifier, const Vec2& point) mutable {
        if (rectangle.containsPoint(point)) {
            identifierCollection[identifier] = true;
            anonymousHeld();
        } else {
            holdFinish(identifier);
        }
    });
    holdFinishCollection.push_back(holdFinish);
}

I can see in the debugger that holdFinish is pointing to a different implementation of identifierCollection than in the 2nd lambda function.

If I use [=, &identifierCollection] it throws a EXC_BAD_ACCESS whether I use mutable or not.

My experience with other languages that implement inline functions is that this should be possible. For instance in javascript:

var a = 10;
var b = function() {
    a += 2;
}
var c = function() {
    a += 3;
}
b();
c();
alert(a);

Would alert 15.

What do I have to do to get both lambda functions to reference the same identifierCollection implementation? So that it behaves in the same way as the javascript example.

2

There are 2 answers

2
Lightness Races in Orbit On BEST ANSWER

Unlike in some scripting languages, identifierCollection's lifetime won't be extended simply because you captured it into a closure. So as soon as you change that [=] for a [&] to capture by reference, it's a dangling reference to a local variable that you're capturing.

You'll have to manage the lifetime of identifierCollection yourself; frankly, this sounds like the perfect opportunity for a shared pointer, captured by value into each lambda. The dynamically-allocated map it wraps will literally exist for as long as you need it to.

void holdAdd(const Rect& rectangle, Hold anonymousHeld, Hold anonymousFinish)
{
    auto identifierCollection = std::make_shared<std::map<int,bool>>();

    HoldFinish holdFinish = [=](const int& identifier) mutable {
        if (identifierCollection->count(identifier) == 0) return;

        identifierCollection->erase(identifier);
        anonymousFinish();
    };

    holdCollisionCollection.push_back([=](const int& identifier, const Vec2& point) mutable {
        if (rectangle.containsPoint(point)) {
            (*identifierCollection)[identifier] = true;
            anonymousHeld();
        } else {
            holdFinish(identifier);
        }
    });
    holdFinishCollection.push_back(holdFinish);
}
0
Jonathan Potter On

If you wrap the map in a std::shared_ptr then the lifetime will be managed automatically. Your lambda can then capture by value and it will get a reference to the map whose lifetime remains valid until the lambda function returns.

To do this, change your map definition to:

auto identifierCollection = std::make_shared<std::map<int,bool>>();

And then any calls to member functions of the map need to change from using . to -> (as it is now a pointer).