функции как значенияfunctions as values

lambda expressions

Анонимная функция, которую можно определить прямо в месте использования, с возможностью захвата переменных из окружающего скоупа. Конец ручных функторов для передачи в алгоритмы STL.An anonymous function that can be defined right at its point of use, with the ability to capture variables from the enclosing scope. The end of hand-written functors for passing into STL algorithms.

часто используетсяcommonly used уникальный безымянный типunique unnamed type

Что этоWhat it is

Лямбда-выражение [захват](параметры) -> тип_возврата { тело } — компактный синтаксис для определения безымянного функционального объекта (замыкания) на месте. Компилятор генерирует под лямбдой уникальный класс с перегруженным operator(), содержащий захваченные переменные как поля.A lambda expression, [capture](parameters) -> return_type { body }, is compact syntax for defining an unnamed function object (a closure) inline. Under the hood, the compiler generates a unique class with an overloaded operator(), holding the captured variables as fields.

до C++11 — функтор вручнуюbefore C++11 — a hand-written functor
struct GreaterThan {
    int threshold;
    GreaterThan(int t) : threshold(t) {}
    bool operator()(int x) const { return x > threshold; }
};
std::count_if(v.begin(), v.end(), GreaterThan(10));
с lambdawith a lambda
int threshold = 10;
std::count_if(v.begin(), v.end(),
    [threshold](int x) { return x > threshold; });

Зачем ввелиWhy it was introduced

Лямбды закрывают разрыв между алгоритмами STL и удобством их использования:Lambdas close the gap between STL algorithms and the convenience of actually using them:

01

Функторы требовали объявления вне места использованияFunctors required a declaration away from the point of use

Класс-функтор для одноразового предиката в std::sort или std::find_if приходилось объявлять отдельно от вызова — логика оказывалась разнесена по коду, хотя использовалась в одном месте.A functor class for a one-off predicate passed to std::sort or std::find_if had to be declared separately from the call — the logic ended up scattered across the file even though it was only used in one place.

02

Захват переменных вручную — шаблонный кодManual variable capture was boilerplate

Функтор, которому нужны внешние переменные, требовал конструктора и полей для их хранения — механическая работа, которую лямбда делает автоматически через список захвата [x, &y].A functor that needed external variables required a constructor and fields to hold them — mechanical work that a lambda handles automatically through its capture list, [x, &y].

03

function pointer не мог захватывать состояниеA function pointer couldn't capture state

Обычный указатель на функцию, который использовался как более лёгкая альтернатива функтору, вообще не может нести с собой контекст — лямбда с непустым захватом решает именно эту задачу, оставаясь синтаксически лёгкой.An ordinary function pointer, used as a lighter alternative to a functor, simply cannot carry any context along with it — a lambda with a non-empty capture solves exactly this problem while staying syntactically light.

Как это работает под капотомHow it works under the hood

Каждое лямбда-выражение порождает свой уникальный, безымянный тип класса (closure type) с operator() const (без mutable) — вызов лямбды и есть вызов этого метода. Переменные из списка захвата становятся полями этого класса: захват по значению [x] — копия-поле, инициализируемая в момент создания лямбды; захват по ссылке [&x] — поле-ссылка.Every lambda expression produces its own unique, unnamed closure type with an operator() const (without mutable) — calling the lambda means calling that method. Variables from the capture list become fields of that class: capture by value [x] becomes a copy field, initialized at the moment the lambda is created; capture by reference [&x] becomes a reference field.

int threshold = 10;

auto pred = [threshold](int x) { return x > threshold; };

// компилятор генерирует примерно это (концептуально):// the compiler generates roughly this (conceptually):
struct __lambda_anon {
    int threshold;                       // копия — захват по значению// a copy — capture by value
    __lambda_anon(int t) : threshold(t) {}
    bool operator()(int x) const { return x > threshold; }
};
auto pred = __lambda_anon(threshold);

// [=] — захват всего использованного по значению// [=] — capture everything used, by value
// [&] — захват всего использованного по ссылке// [&] — capture everything used, by reference
// []  — без захвата: тип неявно конвертируется в указатель на функцию// []  — no capture: the type implicitly converts to a function pointer
void (*fp)() = []() { /* без захвата *//* no capture */ };

Тип лямбды уникален и не имеет письменного обозначения в языке — сохранить её можно только через auto или через type-erasure обёртку std::function (с ценой виртуального вызова и обычно allocation на куче).A lambda's type is unique and has no spellable name in the language — the only way to store it is through auto, or through the type-erasure wrapper std::function (at the cost of a virtual call and usually a heap allocation).

ГраблиGotchas

Захват по ссылке переживает область видимостиReference capture can outlive its scope

[&x] хранит ссылку на переменную из внешнего скоупа — если лямбда переживёт эту переменную (например, сохранена в std::function и вызвана позже, а x была локальной), обращение к ней — use-after-scope, undefined behavior.[&x] stores a reference to a variable from the enclosing scope — if the lambda outlives that variable (say, it's stored in a std::function and invoked later, while x was local), accessing it is a use-after-scope, undefined behavior.

Generic lambda — это C++14, не C++11Generic lambdas are C++14, not C++11

[](auto x) { return x + 1; } с параметром auto компилируется только начиная с C++14. В C++11 параметры лямбды должны иметь конкретный тип, как у обычной функции.[](auto x) { return x + 1; } with an auto parameter only compiles starting with C++14. In C++11, lambda parameters must have a concrete type, just like an ordinary function.

operator() лямбды const по умолчаниюof a lambda is const by default

Захваченные по значению переменные нельзя изменить внутри тела лямбды без ключевого слова mutable — попытка присвоить значение полю-копии без mutable не компилируется, что неочевидно при первом знакомстве с лямбдами.Variables captured by value can't be modified inside the lambda's body without the mutable keyword — attempting to assign to the copied field without mutable won't compile, which is not obvious the first time you encounter lambdas.