PrevNext

(Optional) C++ - Lambda Expressions

Authors: Benjamin Qi, Dong Liu, Justin Ji

Defining anonymous function objects.

Edit This Page

Introduction

Why Use Lambdas?

Lambdas allow us to write simple and anonymous functions in place. This allows us to write smaller functions while keeping code local and organized. In addition, lambdas can capture their surrounding variables, which is often convenient when writing helper functions.

In addition, the C++ standard library has good support for lambdas, with functions like std::sort and std::lower_bound allowing you to pass custom functions to act as a comparator of objects.

General Form

Lambdas have the following form:

[capture_list](parameters) -> trailing_return_type {
	// function body
}

The parameters, return type, and function body are all pretty straightforward. In competitive programming contexts, we typically use the following capture types:

[]: Does not capture anything in the local scope.

[&]: Captures everything in the local scope by reference.

[=]: Captures everything in the local scope by copy.

The lambda's local scope is the scope where it is defined, not the scope where it is used.

You can also specify what variables to capture, but this typically is not necessary.

For an example of a lambda, say we want to write a function returns the square of a given number. We can write the following lambda expression:

auto square = [](int x) -> long long { return (long long)x * x; };

Then, you can call the lambda like a normal function.

cout << square(10) << '\n'; // prints out 100

Recursive Lambdas

Let's say we want to write a recursive GCD function. With a lambda, the most straightfoward way to write it would be like this:

auto gcd = [](int a, int b) -> int { return b == 0 ? a : gcd(b, a % b); };

Unfortunately, this does not work, because a lambda cannot directly reference itself in its definition. However, there are workarounds to this issue.

With y_combinator

Resources
open-std
RIP Tutorial

If we add the following from the link above in C++14:

namespace std {
template <class Fun> class y_combinator_result {
Fun fun_;
public:
template <class T>
explicit y_combinator_result(T &&fun) : fun_(std::forward<T>(fun)) {}
template <class... Args> decltype(auto) operator()(Args &&...args) {

Then we can have code like the following:

int main() {
cout << y_combinator([](auto gcd, int a, int b) -> int {
return b == 0 ? a : gcd(b, a % b);
})(20, 30)
<< "\n"; // outputs 10
}

With std::function

Instead of auto, use function<return_type(param)>.

int main() {
function<int(int, int)> gcd = [&](int a, int b) {
return b == 0 ? a : gcd(b, a % b);
};
cout << gcd(20, 30) << '\n'; // outputs 10
}

With auto as a parameter:

The main issue with recursive lambdas is that a lambda typically cannot access itself. To fix this, we pass the lambda into itself.

int main() {
auto gcd = [&](int a, int b, auto &&gcd) -> int {
return b == 0 ? a : gcd(b, a % b, gcd);
};
cout << gcd(20, 30, gcd) << '\n'; // outputs 10
}

Module Progress:

Join the USACO Forum!

Stuck on a problem, or don't understand a module? Join the USACO Forum and get help from other competitive programmers!

PrevNext