Lambdas in C++11

Lambda expressions have been introduced to C++ with the most recent standard, presented in Section [expr.prim.lambda]. They allow the creation of simple functions without giving them a name. What are they good for? Being simple is not really the point here; the most useful case that I have found in my limited experience has been as a convenient replacement for Functors (also known as Function objects).

In this post, I’ll give a quick introduction to how to use lambdas in your code. In the next installment, I will discuss a small example of functor, and will show side by side a piece of code with functors and its substitution with a clean lambda.

How do I write one? (Syntax)

Maybe the simplest compilable (tried with gcc 4.8.1 with option -std=c++0x) lambda has the following syntax:

1
[](){};

This lambda however is not very useful, so we might try something a tad harder:

1
[](){ return 1; };

This is a simple function that returns 1. What if you want to use a parameter? You would do:

1
[](int i){ return i+1; };

This is a simplistic incrementer of an int variable.

Things start becoming interesting when we start using the capture feature. Consider the following snippet:

1
2
int x = 1;
[=x](){ return x+1; };

This just says: capture the value of the variable x from the outer scope, and return its value plus 1. The obscure rune between brackets is just the symbol = followed by the name of the variable, that means: capture by value. If we want to actually change the value of the variable, we need to capture by reference. This is promptly done:

1
2
int x = 1;
[&x](){ x = x+1; return x; };

We just use the symbol &, and we are done. Note that the code snippets above actually do not do anything useful, because we are just declaring the lambda without ever using it. Notice however that this behavior is not considered an error by the compiler, even at the highest warning level (which you always enable, right?).

There are a couple of shortcuts: if we want to capture all the variables by value, we can just write a lonely = between brackets; the same applies to the case of capture “everything by reference”. In particular, if we want to capture everything by value, but y, which we want by reference, we can write:

1
2
3
4
int x = 1;
int y = 0;
int z = 0;
[=, &y](){ y = x + z; return y; }

Return of a value

The truth is, so far we have been using a shortcut. In a lambda we can also specify a return value; if we do not, then the return type is determined by the following rule: if there is a return statement with an expression, then the return type is the result of the expression after its evaluation; otherwise it is void. To specify explicitly a return type (useful when dealing with template types), we use the syntax

1
[=, &y]() -> int { y = x + z; return y; }

By the way, if we have no parameters we can omit the parentheses:

1
[=, &y] { y = x + z; return y; }

Passing lambdas around: The std::function

Now let’s see the most interesting use of lambdas, or their interactions with STL algorithms. Consider the following snippet (the complete source is here):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Print the string if it satisfied the constraint f
void filter(const vector<string>& v, function<bool(string)> f) {
int count = 0;
for (auto s : v) {
if (f(s)) {
cout << "'" << s << "' ";
++count;
}
}
if (count > 0)
cout << '\n';
}

int main() {
ifstream f("/home/gc/Downloads/pg44327.txt");

if (!f) {
cout << "Error: can't open file\n";
exit(EXIT_FAILURE);
}
string line;
while (getline(f, line)) {
vector<string> v = Split(line);
filter(v, [](const string& s) { return s.size() >= 3; });
}
}

Consider what is done there in the main function: the function filter takes a vector and a lambda. The signature features the template std::function (to be found in the header <functional>, and is used to simplify a parameter that takes a string as only argument, and returns a bool. Now have a look at the way the function filter: the second argument is a lambda that takes a string argument and, not surprisingly, returns a bool (returns true if the string is longer than 3).

For completeness, here is the text of the function Split. Watch out: it is far from perfect (consider what happens when 2 consecutive characters occur, which are equal to the ones I discard; plus, it is not i18n-ready). It should split a line into words:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
vector<string> Split(const string& line) {
vector<string> res;
size_t pos = line.find_first_of(" [];,.");
size_t oldpos = 0;
while (pos != string::npos) {
if (pos != oldpos) {
res.push_back(line.substr(oldpos, pos - oldpos));
}

oldpos = pos + 1;
pos = line.find_first_of(" [];,.", oldpos);
}
return res;
}

Using it in algorithms

We can actually do the work of the previous example with less code, using STL algorithms:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <functional>
#include <algorithm>
#include <cstdlib>
#include <vector>
#include <string>
#include <fstream>

using std::function;
using std::string;
using std::cout;
using std::generate;
using std::for_each;
using std::vector;
using std::endl;
using std::ifstream;

int main() {
ifstream f("/home/gc/Documents/diary/cervantes-20130202.txt");
if (!f) {
cout << "Error: can't open file\n";
exit(EXIT_FAILURE);
}
string line;
while (getline(f, line)) {
vector<string> v = Split(line);
const int value = 5;
int cnt = count_if(v.cbegin(), v.cend(), [](const string& s) { return s.size() >= value; });
cout << cnt << " words longer than " << value << "\n";
}
}

Here the interesting line is the one that sports the call to std::count_if. It is an algorithm that takes a range and a function that expresses a condition that, if true, makes it count the element. The result is accumulated in the cnt variable.

As another example, look at this extremely concise way of printing a container:

1
2
3
vector<string> line;
// ... push_back of values
for_each(line.cbegin(), line.cend(), [](const string& s) { cout << s << "\n"; });

The for_each algorithm takes a range and performs the action determined by the lambda passed as third argument. I find this really neat!

Conclusions

But so what is the real advantage of using lambdas over functors?

In the next installment, we will see an example of functor, and which advantage we actually get from using lambdas over functors.