Лямбды: первое знакомство
Определить функцию внутри другой функции нельзя: int twice(int x) { return x * 2; } внутри main компилятор не примет. Но короткая функция часто нужна в одном месте, и выносить её из main неудобно. Такую функцию можно записать лямбдой прямо там, где она нужна. Это как записка на стикере, приклеенная к месту работы, вместо отдельной инструкции в шкафу:
#include <iostream>
int main() {
auto twice = [](int x) { return x * 2; };
std::cout << twice(21) << '\n'; // 42
}Устройство лямбды
Лямбда — функция без имени, записанная прямо внутри выражения. Лямбда состоит из трёх частей:
- квадратные скобки
[]начинают лямбду; - в круглых скобках стоят параметры;
- в фигурных скобках стоит тело.
Тип результата компилятор выводит из return, как тип auto-переменной — из её значения. В примере x * 2 имеет тип int, поэтому и лямбда возвращает int.
Лямбду сохраняют в переменную, объявленную через auto: тип у лямбды есть, но назвать его в коде нельзя. Каждая лямбда получает свой тип, который придумывает компилятор. Вызывают лямбду по имени переменной, как обычную функцию: twice(21). Параметры лямбды, как и параметры функции, передают по значению или по ссылке.
Захват переменных
Чтобы лямбда могла использовать локальную переменную step из main, переменную нужно захватить. Захват — список переменных, которые лямбда берёт из окружающего кода; его пишут в квадратных скобках. Захватить переменную можно двумя способами:
[step]копируетstepв момент создания лямбды, как фотография доски: что было написано тогда, то и останется на снимке;[&step]даёт лямбде ссылку на самуstep, как окно, через которое доску видно всегда:
#include <iostream>
int main() {
int step = 10;
auto add_copy = [step](int x) { return x + step; };
auto add_ref = [&step](int x) { return x + step; };
step = 100;
std::cout << add_copy(1) << ' ' << add_ref(1) << '\n'; // 11 101
}add_copy хранит копию со значением 10: она сделана, когда выполнялась строка с лямбдой, поэтому изменение step до неё не доходит. add_ref читает саму step, которая к моменту вызова уже равна 100.
Через захват по ссылке лямбда может и менять переменную: [&count]() { ++count; } при каждом вызове увеличивает count из main. Копию из [step] лямбда может только читать: [step]() { ++step; } не скомпилируется. Это тот же выбор, что у параметров функции: копия или другое имя той же переменной. Несколько переменных перечисляют через запятую: [a, &b].
Частая ошибка
Переменную используют в лямбде, но не захватывают:
int main() {
int step = 10;
auto add = [](int x) { return x + step; }; // ✗
return add(1);
}error: variable 'step' cannot be implicitly captured in a lambda with no capture-default specifiedЭто ошибка компилятора: пустые скобки [] значат, что лямбда ничего не захватывает. Добавьте переменную в захват — [step] или [&step].
Главное
- Лямбда — функция без имени:
[]начинают её, в()стоят параметры, в{}— тело. - Лямбду хранят в
auto-переменной и вызывают по её имени, как обычную функцию. [step]копирует переменную при создании лямбды, а[&step]читает саму переменную при каждом вызове.- Переменная, которую лямбда использует, но не захватила, даёт ошибку компилятора.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.