Playing with C++0x -- lambdas
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 with
operator(). 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