функции как значенияfunctions as values
Анонимная функция, которую можно определить прямо в месте использования, с возможностью захвата переменных из окружающего скоупа. Конец ручных функторов для передачи в алгоритмы 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.
Лямбда-выражение [захват](параметры) -> тип_возврата { тело } — компактный синтаксис для определения безымянного функционального объекта (замыкания) на месте. Компилятор генерирует под лямбдой уникальный класс с перегруженным 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.
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));
int threshold = 10;
std::count_if(v.begin(), v.end(),
[threshold](int x) { return x > threshold; });
Лямбды закрывают разрыв между алгоритмами STL и удобством их использования:Lambdas close the gap between STL algorithms and the convenience of actually using them:
Класс-функтор для одноразового предиката в 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.
Функтор, которому нужны внешние переменные, требовал конструктора и полей для их хранения — механическая работа, которую лямбда делает автоматически через список захвата [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].
Обычный указатель на функцию, который использовался как более лёгкая альтернатива функтору, вообще не может нести с собой контекст — лямбда с непустым захватом решает именно эту задачу, оставаясь синтаксически лёгкой.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.
Каждое лямбда-выражение порождает свой уникальный, безымянный тип класса (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).
[&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.
[](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.
Захваченные по значению переменные нельзя изменить внутри тела лямбды без ключевого слова 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.