Taylor
Taylor

Reputation: 2087

When are capture variables captured?

I am making a std::vector of callback std::functions, and I'm having a little trouble understanding the captures. They seem to be going out of scope when I try to use them if I capture by reference. If I capture by value, everything works.

The code that uses these callback functions expects a certain signature, so assuming I can't modify the code that's using these, I need to stick with capture variables instead of passing things as function arguments.

When is localVar being captured? Is it when the lambda is defined, or when it is called? Does the answer change depending on whether I capture by value or reference?

Here's a little example that I would like to understand:

#include <iostream>
#include <functional>
#include <vector>

int main(int argc, char **argv)
{

    int n(5);

    // make a vector of lambda functions
    std::vector<std::function<const int(void)> > fs;
    for(size_t i = 0; i < n; ++i){
        int localVar = i;
        auto my_lambda = [&localVar]()->int // change &localVar to localVar and it works
        {
            return localVar+100;
        };
        fs.push_back(my_lambda);
    }

    // use the vector of lambda functions
    for(size_t i = 0; i < n; ++i){
        std::cout << fs[i]() << "\n";
    }


    return 0;
}

Upvotes: 1

Views: 170

Answers (2)

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385174

They seem to be going out of scope when I try to use them if I capture by reference

That's right. You created a lambda that encapsulates a reference to a local variable. The variable went out of scope, leaving that reference dangling. This is no different to any other reference.

Capturing "happens" at the point where you define the lambda — that is the purpose of it! If it occurred later, when you call the lambda (which time?), the things you wanted to capture would be long gone, or at least unreachable.

Capturing allows us to "save" things that we can name now, for later. But if you capture by reference, you'd better ensure the thing referred-to still exists when you come to use that reference.

Watch out for weirdnesses like this, though.

Upvotes: 4

Fran&#231;ois Andrieux
Fran&#231;ois Andrieux

Reputation: 29022

The reference is captured when you create the lambda. The value of the referred object is never captured. When you call the lambda, it will use the reference to determine the referred object's value whenever you use it (like using any other reference). If you use the reference after the referred object ceases to exist, you are using a dangling reference, it's undefined behavior.

In this case, auto my_lambda = [&localVar]()->int creates a lambda with a reference named localVar to the local variable localVar.

std::cout << fs[i]() << "\n"; calls one of the lambdas. However, when the lambda executes return localVar+100;, it's trying to use the reference localVar to the local variable localVar(local to the first for loop) but that local variable no longer exists. You have undefined behavior.

If you drop the ampersand and take localVar by value (auto my_lambda = [localVar]()->int), you will instead capture a copy of the value as it is at the moment the lambda is created. Since it's a copy, it doesn't matter what happens to the original localVar.

You can read about this at http://en.cppreference.com/w/cpp/language/lambda#Lambda_capture

Upvotes: 8

Related Questions