0%

Lambda Expressions in C++: What are They?

This is my learning notes from the course - Mastering C++ Standard Library Features.

Discover Lambdas

  • Closures and lambda expressions
  • Lambda expression syntax in depth
  • How lambda expressions work and their performance
  • Example use cases and guidelines

Lambda Expressions in C++: What are They?

  • Examples of problems solved by lambda expressions
  • Definition of lambda expression and closure

Examples of the Lambda Expression

Example: Sorting by Member Variable

We want to sort people by the age variable.

1
2
3
4
5
6
7
8
9
10
11
struct person
{
std::string _name;
int _age;
};

int main()
{
std::vector<person> people{/* ... */};
std::sort(std::begin(people), std::end(people), ???);
}

One way we could solve this in C++03 is by creating a Function Object called by_age. This struct will expose an operator call overload that takes a person a and a person b and returns whether the age of the first one is less than the age of the second one.

1
2
3
4
5
6
7
struct by_age
{
bool operator()(const person& a, const person& b) const
{
return a._age < b._age;
}
};
1
2
std::vector<person> people{/* ... */};
std::sort(std::begin(people), std::end(people), by_age{});

In C++11 and further standards of the language, you can avoid all that boilerplate by using a lambda expression.

You can see in the following example that, as the third argument of std::sort, I’m using this new syntax that creates a Function Object on the spot and expresses a predicate for an action that we need to execute to sort the vector.

In this example, the lambda expression takes two arguments, person a and person b, and returns a comparison between their ages. If we go back, you can see the similarities between the Function Obejct and the lambda expression.

1
2
3
4
5
6
std::vector<person> people{/* ... */};
std::sort(std::begin(people), std::end(people),
[](const person& a, const person& b)
{
return a._age < b._age;
});
  • Lambda expressions allow you to express actions and predicates with minimal boilerplate.

Example: Reducing Code Repetition

1
2
3
4
5
6
7
8
9
10
11
12
void process(const person& p)
{
if(some_predicate(p._name))
{
some_action(p._name);
}

if(some_predicate(p._age))
{
some_action(p._age);
}
}

As you can see, this pattern of calling the predicate and then executing an action is repeated, first the name and then the age. Lambda expressions can be used as short local functions that can really help removing code repetition and increasing readability.

1
2
3
4
5
6
7
8
9
10
void process(const person& p)
{
const auto process_field = [](const auto& x)
{
if(some_predicate(x)) { some_action(x); }
};

process_field(p._name);
process_field(p._age);
}
  • Short lambdas can be used as “local functions“.

Example: Asynchronous Pipelines

1
2
3
4
5
6
7
8
auto fut = http_get_request("somewebsite.com")
.then([](payload p){ return some_other_request(p); })
.then([](payload p){ return read_file(p._filename); })
.then([](std::string data){ std::cout << data; })

// ...

fut.get();
  • Lambda expressions make it easy to compose pipelines of actions

What is a Lambda Expression?

  • A lambda expression produces a closure
  • A closure is an unnamed function object capable of capturing variables in scope
1
2
const auto l = []{ std::cout << "hello!\n"; }
l(); // prints "hello!"

Closure: Capturing by Reference

1
2
3
4
5
6
7
8
std::string s = "hello!";
const auto l = [&s]{ std::cout << s << "\n"; };

l(); // prints "hello!"

s = "bye!";

l(); // prints "bye!"

Closure: Capturing by Value

1
2
3
4
5
6
7
8
std::string s = "hello!";
const auto l = [s]{ std::cout << s << "\n"; }; // <- s will be captured by copy inside the closure

l(); // prints "hello!"

s = "bye!";

l(); // prints "hello!"

Recap

  • Lambda expressions are expressions that produce closures
  • Closures are instances of unnamed function objects
  • Variables in scope can be captured by value or by reference from lambdas
  • Lambdas tersely express actions and predicates
  • Some common use cases for lambdas are:
    • passing an action or a predicate to an algorithm function, avoiding code repetition
    • writing more declarative code, as in the example of asynchronous pipelines