1

I have a function that takes a vector of objects that store lambdas as class members. The class also has a static function that takes a vector of these objects and creates a lambda that sums the values of each object's lambda. To test the builder function I've created a test fixture struct with stores a vector of objects. When I use the builder function with the vector stored in the struct I get weird return values from the constructed lambda; when I create a vector of objects outside of a struct the return values are fine. Here is an example code that reproduces the situation:

#include <iostream>
#include <vector>

class LambdaBuilder {
    double m_;
    double b_;
protected:
    // lambda member
    std::function<double(double)> lambda_function
        = [this](double x){return m_*x+b_;};
public:
    // sums the lambda member in a vector of LambdaBuilders
    LambdaBuilder(double m, double b) : m_(m), b_(b){};

    static std::function<double(double)>
    buildFunction(const std::vector<LambdaBuilder> &lambdas){
        auto ret = [&lambdas](double x){
            double val = 0;
            for (auto & lam : lambdas)
                val += lam.lambda_function(x);
            return val;
        };
        return ret;
    }
};

// constructs and stores a vector of LambdaBuilders
struct Fixture{
    std::vector<LambdaBuilder> lambdas;
    Fixture(){
        LambdaBuilder lambda_0(1,2);
        lambdas.push_back(lambda_0);
    }
};

int main(int argc, const char * argv[])
{
    Fixture fixture;
    auto built_function_fixture = LambdaBuilder::buildFunction(fixture.lambdas);

    LambdaBuilder lambda_0(1,2);
    std::vector<LambdaBuilder> lambdas = {lambda_0};
    auto built_function_vector = LambdaBuilder::buildFunction(lambdas);

    std::cout << "Function from Feature fixture: "
              << built_function_fixture(5) << std::endl;
    std::cout << "Function from Feature vector: "
              << built_function_vector(5) << std::endl;
    std::cout << "Should be: " << 1*5 + 2 << std::endl;

    return 0;
}

The code outputs

Function from Feature struct: 3.47661e-309
Function from Feature vector: 7
Should be: 7
Program ended with exit code: 0

I'm a little new to C++11 and I'm not quite sure what is causing the weird results from the fixture.

dyp
  • 38,334
  • 13
  • 112
  • 177
  • You're capturing `lambdas` by reference in `buildFunction` and then return that lambda. The parameter is captured by reference, hence accessing it is UB after returning. This is a bit weird, see e.g. http://stackoverflow.com/q/21443023/420683 – dyp Jul 10 '14 at 16:22
  • 1
    I guess the actual problem has to do with capturing `this` in `lambda_function` and then copying the object (in the `Fixture` ctor). If you replace the `push_back` with an `emplace(1,2)`, the output for both if the same. – dyp Jul 10 '14 at 16:26
  • Excellent, thanks! Replacing the `lambdas.push_back(lambda_0)` with `lambdas.emplace(lambdas.begin(),1,2)` did the trick. – E. J. Winkleberry Jul 10 '14 at 16:39

2 Answers2

2

A lambda capturing this can be thought of as having a pointer member. When you copy that lambda, you copy the pointer.

class my_lambda
{
private:
    LambdaBuilder* that;
public:
    double operator()(double x) const
    { return that->m_*x+that->b_; }
};

This lambda has pointer semantics. Consequently, LambdaBuilder does not have value semantics either:

class LambdaBuilder {
    double m_;
    double b_;
protected:
    // "lambda member"
    std::function<double(double)> lambda_function
        = [this](double x){return m_*x+b_;};
public:
    // sums the lambda member in a vector of LambdaBuilders
    LambdaBuilder(double m, double b) : m_(m), b_(b){};
    LambdaBuilder(LambdaBuilder const&) = default;
};

The default copy constructor will perform a member-wise copy. The copy of the std::function member will copy the lambda. The copied lambda's pointer to the captured this still refers to the source object, not to the target LambdaBuilder.

So when copying a LambdaBuilder, you create a new LambdaBuilder that indirectly points to the former:

struct Fixture{
    std::vector<LambdaBuilder> lambdas;
    Fixture(){
        LambdaBuilder lambda_0(1,2); // original object
        lambdas.push_back(lambda_0); // create a copy referring to `lambda_0`
    }
};

This is dangerous of course, and leads to UB since lambda_0 goes out of scope before the lambda in the vector is used. The "workaround" I suggested in the comments only happens to work because the vector is large enough, never copied etc. It is still very dangerous.


A solution depends on what you want to achieve with the protected member.

dyp
  • 38,334
  • 13
  • 112
  • 177
1

With a little tracing

protected:
    // lambda member
    std::function<double(double)> lambda_function
        = [this](double x){
            std::cout << "lambda called with captured this = " 
                      << this << std::endl;
            return m_*x+b_;

        };
public:
    // sums the lambda member in a vector of LambdaBuilders
    LambdaBuilder(double m, double b) : m_(m), b_(b){
            std::cout <<  " (double,double) ctor for " 
                      << this << std::endl; 
    };

   ~LambdaBuilder () {
       std::cout << "dtor for " << this  << std::endl; 
    }

one finds

 (double,double) ctor for 0x7fff8955b6c0
dtor for 0x7fff8955b6c0
 (double,double) ctor for 0x7fff8955b770
dtor for 0x7fff8955b7e0
lambda called with captured this = 0x7fff8955b6c0
Function from Feature fixture: 3.47663e-309
lambda called with captured this = 0x7fff8955b770
Function from Feature vector: 7
Should be: 7
dtor for 0x24480d0
dtor for 0x7fff8955b770
dtor for 0x2448030

So the first lambda invocation references an already deleted object (@ 0x7fff8955b6c0) in its closure.

Addendum Jul 11th 2014

Th most simple solution appears to be providing an custom copy-ctor

LambdaBuilder(const LambdaBuilder& rhs) : m_(rhs.m_), b_(rhs.b_) 
                        /*, lambda_function(rhs.lambda_function) */{
#ifdef TRACE_IT
            std::cout <<  "custom copy ctor for " << this << std::endl; 
#endif
};

that skips copying the lambda_function, and thus implicitly triggers the lambda_function creation in current context.

With g++ 4.8.3 (-std=c++11 -Wall -pedantic -O0 -pthread)this prints

(double,double) ctor for 0x7fffe68870f0
custom copy ctor for 0x2373030
dtor for 0x7fffe68870f0
(double,double) ctor for 0x7fffe68871a0
custom copy ctor for 0x7fffe6887210
custom copy ctor for 0x23730d0
dtor for 0x7fffe6887210
lambda called with captured this = 0x2373030
Function from Feature fixture: 7
lambda called with captured this = 0x23730d0
Function from Feature vector: 7
Should be: 7
dtor for 0x23730d0
dtor for 0x7fffe68871a0
dtor for 0x2373030
Solkar
  • 1,228
  • 12
  • 22