Today I'm having a look at lambdas. Lambdas are available in g++-4.5, for now only available in experimental.
What are lambdas?
You can think of it as something akin to a struct withoperator(). But it's a more than that: it's closer from a closure, something that Scheme fans are very familiar with: the closure code block captures its outter environment in its body.
Closures are a very powerful tool that are being retrofitted in many languages (for instance Java). They can be used as building blocks for many useful programing idioms such as continuations.
Enough for theory, let's have a look at this new beast.
How do they look like?
[](int i) { return i + 1; };
This is the increment lambda. You can declare a lambda in any function or method. Written like this, the lambda compiles[1] but it's not very useful: it's an anonynous lambda that is neither stored nor used.
How to read this? The square brackets open the declaration of a lambda. Then you declare lambda parameters and its body. Nothing suprising so far except for the declaration opening.
Using a lambda
To use it add arguments, for instance:
[](int i) { return i + 1; }(0);
would compute the value 1.
Storing a lambda
The type of a lambda is automatically deduced. You can hint its return type if you need to. To store it you need to use another new C++0x keywork,auto, that was taken back from C:
auto inc = [](int i) { return i + 1; };
std::cout << inc(0) << std::endl;
Notice that calling a named lambda is not different from calling a function. If you need to hint the lambda return type you can use ->:
auto mult = [](int x, double y) -> double { return x * y; };
Capturing environment
You can use lambdas to capture outter environment. A more complex, not working, example:
void f() {
int x = 5;
[](int w) { return w + x; }(0);
}
Tha lambda declaration + invocation does not work, because the x variable we're referring to is declared in the outter scope. To enable capture of the outter scope, we can rewrite the previous example like this:
void f() {
int x = 5;
[=](int w) { return w + x; }(0);
}
The = sign in the square brackets (which are really the capturing clause) means we will copy every variable from the outter scope. Hence we can access x from our anonymous lambda. Now what if you want to change x?
void f() {
int x = 5;
[=](int w) { return w + ++x; }(0);
}
This does not compile: the environment is read-only by default. If you really want to increment x then you need to write:
void f() {
int x = 5;
[=](int w) mutable { return w + ++x; }(0);
}
Yes, mutable. The value returned from the lambda is what you would expect. But guess what? x value is not changed when the lambda returns. Indeed, we've copied outter scope variables by value. To really affect outter scope you need to change the capture clause:
void f() {
int x = 5;
[&](int w) { return w + ++x; }(0);
}
We use the ampersand to capture the environment by reference, and thus we can change x value. We could also be more explicit about what we want to capture:
struct A {
int z;
A() : z(0) {}
void f() {
int x = 5;
[this, &x](int w) { z = w + ++x; }(0);
std::cout << "x: " << x << ", z: " << z << std::endl;
}
};
The lambda in f() explicitely mentions environment variables which are captured by value: this, because we change this->z, and also the local variable x.
Complete example
It's easier to write functionnal-style code. As Sarah just said: 10 years from now everyone will realize how powerful functional programming is, then we'll be the masters of the universe!
#include <algorithm>
#include <iostream>
#include <vector>
int main(int argc, char* argv[])
{
std::vector<int> v = { 1, 2, 3 };
// Show then increment
std::for_each(v.begin(), v.end(), [](int& e) { std::cout << e++ << std::endl; });
// Show content now
std::for_each(v.begin(), v.end(), [](int e) { std::cout << e << std::endl; });
return 0;
}
[1] Use g++-4.5 --std=c++0x to activate C++0x mode
[2] More complete g++ C++0x feature list available here
September 8 2010, 13:57:01 UTC 1 year ago
Finally
I'm actually pretty excited about C++ getting lambdas and closures. I've been using them heavily in JavaScript since I learned the language for my current job a year and a half ago. Once you learn how they can be used, they feel very powerful and expressive. It's sad that it'll probably be ten years before APIs/frameworks like Qt & Gtkmm will be able to take advantage of these cool new c++0x features (since they tend to need to cater to old, broken, out-of-date compilers.) Getting initial compiler support though is a great step forward. Getting the final specification would be huge too. I hope they do finally finish up the final draft for March 2011 (assuming they keep on schedule.)Anonymous
October 1 2010, 11:55:35 UTC 1 year ago
Closing over stack variables
In your second-to-last example:void f() { int x = 5; [&](int w) { return w + ++x; }(0); }you capture
xin the closure by reference. But oncefreturns, isn'txdestroyed since it's on the stack? In which case, thexreferred to in the closure is now pointing to whatever happens to be on the stack where thexinfused to be?Anonymous
October 1 2010, 11:58:03 UTC 1 year ago
Re: Closing over stack variables
Of course, just after posting this, I've noticed that you're not actually returning the closure. Sorry, my mistake!