
Привет, Хабр!
Сегодня рассмотрим лямбда-выражения в C++ и их эволюцию с момента появления в стандарте C++11 и до последних обновлений в C++20.
Лямбда-выражения в C++ — это анонимные функции, которые позволяют писать инлайн-выражения прямо там, где они используются. С их помощью можно легко определять функции обратного вызова, передавать их в функции высшего порядка или использовать для инициализации функциональных объектов.
Лямбда-выражения в C++11
Лямбда-выражение в C++11 можно объявить следующим образом:
auto myLambda = []() { /* тело лямбды */ };
[] называется лямбда-интродуктором и указывает на захват переменных из окружающего контекста. Скобки после интродуктора могут содержать параметры, как у обычной функции, а фигурные скобки ограничивают тело лямбды.
Захват переменных позволяет лямбда-выражениям использовать данные из окружающего контекста. Доступны следующие способы захвата:
[=]захватывает все переменные из области видимости по значению.[&]захватывает все переменные по ссылке.[this]захватывает указательthisдля доступа к членам класса.[var]или[&var]захватывает конкретную переменную по значению или по ссылке соответственно.
Простой пример:
std::vector<int> nums = {1, 2, 3, 4, 5}; std::for_each(nums.begin(), nums.end(), [](int n) { std::cout << n << " "; });
Код выводит все элементы вектора, используя лямбда-выражение в качестве функции обратного вызова.
Захват переменных:
int multiplier = 5; auto multiply = [multiplier](int n) { return n * multiplier; }; std::cout << multiply(10); // Выведет 50
Лямбда захватывает переменную multiplier по значению и использует ее для умножения входного параметра.
Лямбда в C++14
Одна из лучших фич в C++14 была связана с автоматическим выводом типов возвращаемого значения лямбда-выражений. Т.е наконец не обязательно явно указывать тип возвращаемого значения — компилятор может самостоятельно его определить. И кстати, C++14 ввел возможность инициализации захваченных переменных прямо в лямбда-интродукторе
Итак, в C++11 тип возвращаемого значения лямбда-выражения нужно было указывать явно, если оно не было очевидным. В C++14 этого уже не требуется благодаря автоматическому выводу типов:
Пример без C++14:
auto lambda = []() -> int { return 42; };
Пример с C++14:
auto lambda = []() { return 42; }; // тип значения int выводится автоматически
C++14 позволяет инициализировать переменные непосредственно в лямбда-интродукторе:
auto lambda = [x = 42]() { return x; };
x инициализируется значением 42 и захватывается лямбда-выражением.
Пример с автоматическим выводом типов:
std::vector<int> nums = {1, 2, 3, 4, 5}; auto evenCount = std::count_if(nums.begin(), nums.end(), [](int n) { return n % 2 == 0; }); std::cout << "Количество четных чисел: " << evenCount << std::endl;
Лямбда-выражение используется для подсчета четных чисел в векторе, и тип возвращаемого значения выводится автоматически.
Пример с инициализацией захваченных переменных:
auto adder = [value = 10](int x) { return x + value; }; std::cout << adder(5); // 15
Лямбда-выражение захватывает и иницилизирует переменную value, которая затем используется для добавления к переданному аргументу.
Нововведения в C++17
Здесь появилась крутая фича с захватом*this в лямбда-выражениях. Можно захватывать текущий объект по значению, мастхев при работе с классами или объектами
Рассмотрим класс, в котором нужно использовать лямбда-выражение, захватывающее все локальные переменные по значению и текущий объект класса тоже по значению:
class MyClass { public: int value = 10; void printValue() { auto lambda = [=, *this]() { std::cout << "Value: " << value << std::endl; }; lambda(); } }; int main() { MyClass obj; obj.printValue(); // Выведет: Value: 10 }
Лямбда захватывает текущий объект *this по значению и так можно использовать члены класса внутри лямбды.
В C++17 лямбды теперь могут быть выполнены на этапе компиляции(constexpr):
constexpr auto square = [](int n) constexpr { return n * n; }; int main() { constexpr int result = square(5); static_assert(result == 25, "The result must be 25"); }
square является constexpr лямбда-выражением, а так его можно вычислить на этапе компиляции.
Также C++17 наконец стали довольно часто юзать stl. Вообще, можно было юзать и начиная с версии 14, но только в 17 версии это обрело свою массовость:
#include <algorithm> #include <iostream> #include <vector> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; // auto в параметрах лямбда-выражения auto isEven = [](auto n) { return n % 2 == 0; }; auto it = std::find_if(numbers.begin(), numbers.end(), isEven); if (it != numbers.end()) { std::cout << "Первое четное число: " << *it << std::endl; } }
Параметр auto позволяет его использовать с любым типом, поддерживающим операцию %.
C++20
C версии C++20 лямбду можно использовать вместе с шаблонами:
auto genericLambda = []<typename T>(T x) { std::cout << x << std::endl; }; genericLambda(10); // воркает с int genericLambda("Test"); // с const char*
Концепты в C++20 позволили реализовать строгую типизацию и сделать более понятные требования к шаблонам:
#include <concepts> #include <iostream> template<typename T> concept Addable = requires(T a, T b) { { a + b } -> std::convertible_to<T>; }; auto add = []<Addable T>(T a, T b) { return a + b; }; int main() { std::cout << add(1, 2) << std::endl; // 3 // std::cout << add("a", "b") << std::endl; // ошибка компиляции, так как "a" и "b" не удовлетворяют концепту Addable }
Единственная константа в технологиях — это изменение. Лямбда-выражения делают код только более компактным и читаемым, а с каждой новой версией C++ их использование становится все более удобней.
Если вы хотите лучше разобраться в современных возможностях языка C++, рекомендуем обратить внимание на курс «Специализация C++». В программе подробно разбираются ключевые инструменты языка, такие как лямбда‑выражения, шаблоны и концепты, а также практические подходы к их применению.
Чтобы узнать больше о курсах по C++ и получить доступ к записям открытых уроков, переходите в телеграм‑бот.
