C++ lambda: how to 'freeze' values of local variables?

Viewed 430

In the code below, class A has a lambda as a data member, and a getter to this lambda. Is it possible to 'freeze' the local variables of the lambda to the values that are present when the lambda is returned by the getter?

In other words specifically in this example, is it possible to make the code print 6 instead of 150 even after the value of myA.b is changed?

#include <iostream>
#include <functional>

class A
{
    double b;

    std::function<double(double)> myFunction = [=] (double x)
    {
        double localb = b;
        return localb*x;
    };

public:

    void set_b(double value){b = value;};

    std::function<double(double)> get_myFunction(){return myFunction;};    
};

int main()
{
    A myA;
    myA.set_b(2.0);

    std::function<double(double)> retrievedFunction = myA.get_myFunction();

    myA.set_b(50.0);

    std::cout << retrievedFunction(3.0) << std::endl;

    return 0;
}
2 Answers

You have two issues.

One is related to the answer here: C++11 lambdas: member variable capture gotcha, namely that the lambda captures the member variable via this, which is the only way to do it. That means you'll always get the current value of the member b.

Even if you fix this, myFunction is created once at the beginning, meaning it would capture the current (uninitialized!) value of b at creating time.

What you need is to generate the lambda exactly when get_myFunction() is called, and to have it capture the current value of b:

class A
{
    double b;
public:

    void set_b(double value){b = value;};

    std::function<double(double)> get_myFunction() {
        double localb = b;
        return ([=] (double x) { return localb*x; });
    }
};

Here's a way:

#include <iostream>
#include <functional>

class A
{
    double b;

public:

    void set_b(double value) { b = value; }

    std::function<double(double)> get_myFunction() const {
        return [b = b] (double x) { return b*x; };
    }
};

int main()
{
    A myA;
    myA.set_b(2.0);
    std::function<double(double)> retrievedFunction = myA.get_myFunction();
    myA.set_b(50.0);
    std::cout << retrievedFunction(3.0) << '\n';
}

Crucial points:

  • Instead of creating a single std::function when A is constructed, we delay creation of the function until get_myFunction is called. Otherwise we'd only capture the (still uninitialized) value of b at the beginning.

  • We explicitly capture b by value. We can't just say [b] in the capture list because the outer b isn't really a variable, it's just a member of *this and any use of b really means this->b. That's why [=] doesn't work: It captures this by value (which is just a pointer).

    The [b = b] syntax requires C++ 14.

    Another alternative would be [*this], which captures a copy of the whole object. This requires C++ 17.

Related