Home » C++ » What is the lifetime of a C++ lambda expression?

What is the lifetime of a C++ lambda expression?

Posted by: admin November 30, 2017 Leave a comment

Questions:

(I have read What is the lifetime of lambda-derived implicit functors in C++? already and it does not answer this question.)

I understand that C++ lambda syntax is just sugar for making an instance of an anonymous class with a call operator and some state, and I understand the lifetime requirements of that state (decided by whether you capture by value of by reference.) But what is the lifetime of the lambda object itself? In the following example, is the std::function instance returned going to be useful?

std::function<int(int)> meta_add(int x) {
    auto add = [x](int y) { return x + y; };
    return add;
}

If it is, how does it work? This seems a bit too much magic to me – I can only imagine it working by std::function copying my whole instance, which could be very heavy depending on what I captured – in the past I’ve used std::function primarily with bare function pointers, and copying those is quick. It also seems problematic in light of std::function‘s type erasure.

Answers:

The lifetime is exactly what it would be if you replaced your lambda with a hand-rolled functor:

struct lambda {
   lambda(int x) : x(x) { }
   int operator ()(int y) { return x + y; }

private:
   int x;
};

std::function<int(int)> meta_add(int x) {
   lambda add(x);
   return add;
}

The object will be created, local to the meta_add function, then moved [in its entirty, including the value of x] into the return value, then the local instance will go out of scope and be destroyed as normal. But the object returned from the function will remain valid for as long as the std::function object that holds it does. How long that is obviously depends on the calling context.

Questions:
Answers:

It seems you’re more confused about std::function than lambdas.

std::function uses a technique called type-erasure. Here’s a quick fly by.

class Base
{
  virtual ~Base() {}
  virtual int call( float ) =0;
};

template< typename T>
class Eraser : public Base
{
public:
   Eraser( T t ) : m_t(t) { }
   virtual int call( float f ) override { return m_t(f); }
private:
   T m_t;
};

class Erased
{
public:
   template<typename T>
   Erased( T t ) : m_erased( new Eraser<T>(t) ) { }

   int do_call( float f )
   {
      return m_erased->call( f );
   }
private:
   Base* m_erased;
};

Why would you want to erase the type? Isn’t the type we want just int (*)(float)?

What the type erasure allows is Erased can now store any value that is callable like int(float).

int boring( float f);
short interesting( double d );
struct Powerful
{
   int operator() ( float );
};

Erased e_boring( &boring );
Erased e_interesting( &interesting );
Erased e_powerful( Powerful() );
Erased e_useful( []( float f ) { return 42; } );

Questions:
Answers:

This is:

[x](int y) { return x + y; };

Is equivalent to: (Or can be considered too)

struct MyLambda
{
    MyLambda(int x): x(x) {}
    int operator()(int y) const { return x + y; }
private:
    int x;
};

So your object is returning an object that looks just like that. Which has a well defined copy constructor. So it seems very reasonable that it it can be correctly copied out of a function.

Questions:
Answers:

In the code that you posted:

std::function<int(int)> meta_add(int x) {
    auto add = [x](int y) { return x + y; };
    return add;
}

The std::function<int(int)> object that is returned by the function actually holds a moved instance of the lambda function object that was assigned to local variable add.

When you define a C++11 lambda that captures by-value or by-reference, the C++ compiler automatically generates a unique functional type, an instance of which is constructed when the lambda is called or assigned to a variable. To illustrate, your C++ compiler might generate the following class type for the lambda defined by [x](int y) { return x + y; }:

class __lambda_373s27a
{
    int x;

public:
    __lambda_373s27a(int x_)
        : x(x_)
    {
    }

    int operator()(int y) const {
        return x + y;
    }
};

Then, the meta_add function is essentially equivalent to:

std::function<int(int)> meta_add(int x) {
    __lambda_373s27a add = __lambda_373s27a(x);
    return add;
}

EDIT: By the way, I am not sure if you know this, but this is an example of function currying in C++11.