Проблема
C ++ включает полезные универсальные функции, такие как std::for_each
и std::transform
, которые могут быть очень удобными.К сожалению, они также могут быть довольно громоздкими в использовании, особенно если функтор , который вы хотите применить, уникален для конкретной функции.
#include <algorithm>
#include <vector>
namespace {
struct f {
void operator()(int) {
// do something
}
};
}
void func(std::vector<int>& v) {
f f;
std::for_each(v.begin(), v.end(), f);
}
Если вы используете f
только один раз ив этом конкретном месте кажется излишним писать целый класс просто для того, чтобы сделать что-то тривиальное и однозначное.
В C ++ 03 у вас может возникнуть желание написать что-то вроде следующего, чтобы сохранить функтор локальным:
void func2(std::vector<int>& v) {
struct {
void operator()(int) {
// do something
}
} f;
std::for_each(v.begin(), v.end(), f);
}
однако это не разрешено, f
нельзя передать функции template в C ++ 03.
Новое решение
C ++ 11 представляет лямбда-выражения, позволяющие написать встроенный анонимный функтор для замены struct f
.Для небольших простых примеров это может быть чище для чтения (оно хранит все в одном месте) и потенциально проще для поддержки, например, в простейшей форме:
void func3(std::vector<int>& v) {
std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}
Лямбда-функции являются просто синтаксическим сахаром для анонимных функторов.
Типы возврата
В простых случаях для вас выводится тип возврата лямбды, например:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) { return d < 0.00001 ? 0 : d; }
);
}
, однако, когда вы начнете писать более сложные лямбды, вы получитебыстро встречаются случаи, когда тип возвращаемого значения не может быть определен компилятором, например:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}
Для решения этой проблемы вы можете явно указать тип возвращаемого значения для лямбда-функции, используя -> T
:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) -> double {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}
«Захват» переменных
До сих пор мы не использовали ничего, кроме того, что было передано в лямбду в нем, но мы также можем использовать другие переменные, внутри лямбды.Если вы хотите получить доступ к другим переменным, вы можете использовать предложение захвата ([]
выражения), которое до сих пор не использовалось в этих примерах, например:
void func5(std::vector<double>& v, const double& epsilon) {
std::transform(v.begin(), v.end(), v.begin(),
[epsilon](double d) -> double {
if (d < epsilon) {
return 0;
} else {
return d;
}
});
}
Вы можете захватить обе ссылкии значение, которое можно указать, используя &
и =
соответственно:
[&epsilon]
захват по ссылке [&]
захватывает все переменные, используемые в лямбда-выражении по ссылке [=]
захватывает все переменные, используемые в лямбда-выражении, по значению [&, epsilon]
захватывает переменные, как с помощью [&], но epsilon по значению [=, &epsilon]
захватывает переменныекак с [=], но epsilon по ссылке
Сгенерированный operator()
равен const
по умолчанию, с импликациями захвата будет const
при доступе к ним по умолчанию.Это приводит к тому, что каждый вызов с одним и тем же вводом будет давать один и тот же результат, однако вы можете пометить лямбду как mutable
, чтобы запросить, чтобы полученный operator()
не был const
.