About Blog Contact

Captures in Lambda Expressions

Lambda Expressions are a way to easily define and execute unamed functions, typically for one time use. However, it is very easy to make mistakes while using it. For example, lambda expression’s “capture” of variables can be hard to understand.

Ordinary functions can use all variables that are in the scope where the function is defined. However, lambda expressions can only use functions that are captured. A lambda expression has the syntax of:

[ captures ] ( params ) -> ret { body }	

(params can be omitted to implicate the function taking no arguments, and ->ret can be omitted to make the function behave as if auto is the return type. For example:

int x = 5;
SomeClass y;
auto l = [x, &y](int z) -> int r {
    return  r + x + y.someMethod();
};
auto m = [x] {
   return x * 5; // y cannot be used.
}

The later three parameters are easy to understand. Params correspond to function arguments, ret is the return type (which can be ommitted, and body is where the expression is implemented. Capture, however, is a term not found anywhere else. The captures is a comma-separated list of zero or more captures, which determines what variables can be used inside the body. If you leave it blank, then nothing is captured, and you can only use non-local variables or those with static storage durations, a const integral type and a few other cases. You can use a capture default to capture all variables that are in scope:

For example, to capture all variables by reference:

SomeClass x;
int i = 0;
auto l = [&]() {
    x.someMethod(); // x is passed by reference
    i++; // i is also passed by reference
}
l();
// i is now 1

To capture all variables by value:

SomeClass x;
int i = 0;
auto l = [=]() {
   x.SomeMethod(); // Different copy of x
   i++;
}
l();
// i is still 0

You can set a default and add exception. For example, to capture all variables except i by reference and i by value, use:

auto l = [&, i]{ \\ do stuff here }; 

To capture all variables except SomeClass by value and SomeClass by references, use:

auto l = [=, &SomeClass] { \\ do stuff here };

A side note here, the current object (this) will be captured by value, even if = is used as a capture default. One must explicitly capture it by value if wishes so.

I’m writing about lambda expressions because I had just made an easy to miss mistake:

for(int i = 0; i < n; i++) {
  classArray[i] = [&]() {
      anotherClassArray[i](someVariables); 
  }
}

// Call like this later
anotherClassArray[j]();

I initialized a few class instances and put them into a array, due to the fact that each instance needs its personal copy of variables, and also because anotherClass takes in lots of variables, and I want to present a cleaner interface.

See the problem with my code? Well, the i inside is passed by reference. So when the lambda is call outside the scope of the for loop, the i would not exist. What’s worse, there would not be any problems during compile time and would only fail during runtime with a SEGFAULT or SEGABRT and other cryptic errors. To correct it is easy:

classArray[i] = [&, i]() { ....

now that the i inside is a copy of the outside i. However, my experience shows that the abstraction benefits that comes with syntactic sugar like lambda expression are not worth the troubles it causes.

Further Reading: A post on Stack Overflow