Лямбда выражения [в разработке]
Перевод в другую систему счисления
Представим, что строительная компания поставляет некие детали в разные страны. И в одной из программ в этой компании требуется ей перевести длины деталей из одной системы счисления в другую.
Взгляните на следующий пример кода:
#include <iostream>
#include <vector>
#include <algorithm>
float meters_to_feet(float meters)
{
return meters * 3.28084;
}
float meters_to_sm(float meters)
{
return meters * 100;
}
float meters_to_inches(float meters)
{
return meters * 39.3701;
}
int main()
{
std::vector <float> len_m = {1.84, 2.05, 2.09, 1.64};
std::vector <float> len_f(len_m.size());
std::vector <float> len_s(len_m.size());
std::vector <float> len_i(len_m.size());
std::transform(len_m.begin(), len_m.end(), len_f.begin(), meters_to_feet);
std::transform(len_m.begin(), len_m.end(), len_s.begin(), meters_to_sm);
std::transform(len_m.begin(), len_m.end(), len_i.begin(), meters_to_inches);
}
Здесь без использования лямбда-выражений производится перевод массива длин деталей в метрах в массивы длин деталей в футах, сантиметрах и дюймах. С помощью метода [[std transform|std::transform]] мы обрабатываем начальный массив заданными функциями и записываем результаты работы функции по каждому элементу начального массива в новый массив.
Как итог, мы получили громоздкий и тяжело читаемый код, с большим количеством повторяющихся однотипных функций. Ради одной функции, которая просто умножает float
на коэффициент, приходится писать очень много лишнего кода.
На помощь приходят лямбда-выражения
Взглянем теперь как бы выглядел наш код с использованием лямбда-выражений:
#include <iostream>
#include <vector>
#include <algorithm>
template <typename T>
void print_vector(const std::vector<T> &vec)
{
for (const auto &x : vec)
{
std::cout << x << ' ';
}
std::cout << std::endl;
}
int main()
{
std::vector <float> len_m = {1.84, 2.05, 2.09, 1.64};
std::vector <float> len_f(len_m.size());
std::vector <float> len_s(len_m.size());
std::vector <float> len_i(len_m.size());
std::transform(len_m.begin(), len_m.end(), len_f.begin(),
[](float m) {return m * 3.28084;});
std::transform(len_m.begin(), len_m.end(), len_s.begin(),
[](float m) {return m * 100;});
std::transform(len_m.begin(), len_m.end(), len_i.begin(),
[](float m) {return m * 39.3701;});
print_vector(len_f);
print_vector(len_s);
print_vector(len_i);
}
Нужные нам функции мы "определили" прямо в функции main
! Код стал намного более читаемым, мы избавились от лишних и ненужных повторений.
Применяем для примера capture list
В нашем примере, все три используемых лямбды отличаются только коэффициентом, на который домножается m. Почему бы не вынести этот коэффициент как отдельную внешнюю переменную? Так можно достаточно стремительно расширить функционал программы и сделать её ещё более простой. Сделать это нам помогает capture-list лямбда выражения:
#include <iostream>
#include <vector>
#include <algorithm>
template <typename T>
void print_vector(const std::vector<T> &vec)
{
for (const auto &x : vec)
{
std::cout << x << ' ';
}
std::cout << std::endl;
}
int main()
{
std::vector <float> len_m = {1.84, 2.05, 2.09, 1.64};
std::vector <float> len_f(len_m.size());
std::vector <float> len_s(len_m.size());
std::vector <float> len_i(len_m.size());
std::vector<std::pair<std::vector<float>, float>> coeff_to_vec
= {{len_f, 3.28084}, {len_s, 100}, {len_i, 39.3701}};
for (auto&& [vec, coeff] : coeff_to_vec)
{
std::transform
(
len_m.begin(),
len_m.end(),
vec.begin(),
[&coeff](float m) {return m * coeff;}
);
print_vector(vec);
}
}
В фигурных скобках для лямбда-выражения мы указываем, что лямбда-функция должна захватывать локальную переменную coeff
и использовать её в своих вычислениях.
Как видим, код стал ещё понятнее и проще.
Ну а теперь поговорим более "официально" о том, что такое лямбда-выражение.
Теория
cppreference.com
Constructs a closure: an unnamed function object capable of capturing variables in scope.
Синтаксис лямбда-выражения:
[](){}
- В квадратных скобках указываются переменные для захвата
- В круглых скобках указываются аргументы лямбда-функции
- В фигурных скобках указывается само тело функции. Что значит "захватить" переменные? Это значит, что лямбда может в теле функции использовать переменные из локальной области видимости, если задать, что мы действительно хотим их в ней использовать. Захватить переменные можно несколькими способами:
[=]
- захват лямбдлй всех локальных переменных по значению[&]
- то же самое, но только по ссылке[this]
- захват лямбдой текущего объекта класса (для методов класса). Так можно получить внутри лямбды доступ ко всем методам и полям класса[value1, &value2]
- захват лямбдой конкретных переменных по значению/по ссылке (соответственно) Внутри лямбды можно обращаться к глобальным переменным/статическим переменным какого-либо класса/constexpr
-переменным.
Дополнительные фишки
Можно явно задать тип возвращаемого лямбдой значения:
[](int a) -> int {return a + 3};
Можно так же получить из лямбды указатель на функцию:
+[](){}
Делается это с помощью +
перед лямбдой.
Использование лямбды
- Можно объявить лямбду и сразу вызвать её:
[](){std::cout << "Hello world!\n";}();
2. Можно записать лямбду в локальную переменную:
```cpp
auto lambda = [](){std::cout << "Hello world!\n";};
lambda();
- Поддерживается каст лямбды в указатель на функцию:
void (*f_ptr)() = lambda; f_ptr();
4. Лямбду, как и любой другой callable объект, можно "записать" в `std::function`:
```cpp
std::function<void()> f = lambda;
- Переменную с лямбдой нельзя перезаписывать