Лямбда-выражение может "захватывать" переменные, определенные вне этого выражения в окружающей области. Для этого применяются квадратные скобки, с которых начинается выражение.
Если надо получить все внешние переменные из области, где определено лямбда-выражение, по значению, то в квадратных скобках указывается символ "равно" =. Но в этом случае в лямбда-выражении значения внешних переменных изменить нельзя:
#include <iostream> int main() { int n{10}; auto add = [=](int x) { std::cout << x + n << std::endl; }; add(4); // 14 }
Благодаря выражению [=]
лямбда может получить внешнюю переменную n и использовать ее значение.
Для подобного лямбда-выражения компилятор будет генерировать класс наподобие:
class __Lambda1c8 { public: __Lambda1c8(const int& arg1) : n(arg1) {} auto operator()(int x) const { std::cout << x + n << std::endl; } private: int n; };
И здесь следует отметить пару моментов. Прежде всего, значение внешней переменной передается через параметр, который представляет константную ссылку, и сохраняется в приватную переменную. Другой момент - поскольку все действия лямбда-выражения выполняются в операторе (), который определен как константный, то значение приватной переменной мы изменить не можем. Поэтому внешние переменные передаются по значению - мы можем получить их значение, но изменить его не можем.
Стоит отметить, что хотя мы не можем изменить внешнюю переменную, но мы можем передать по значению указатель на внешнюю переменную и через этот указатель внутри лямбда-выражения изменить значение переменной
#include <iostream> int main() { int n{10}; int* np {&n}; auto increment = [np](){(*np)++;}; increment(); // n = 10 std::cout << "n = " << n << std::endl; // n = 11 }
Если надо получить внешние переменные по ссылке, то в квадратных скобках указывается символ амперсанда &. В этом случае лямбда-выражение может изменять значения этих переменных:
#include <iostream> int main() { int n{10}; auto increment = [&]() { n++; // увеличиваем значение внешней переменной n std::cout << "n inside lambda: " << n << std::endl; }; increment(); std::cout << "n outside lambda: " << n << std::endl; }
Благодаря выражению [&]
лямбда increment может получить внешнюю переменную n по ссылке и изменять ее значение. В данном случае увеличиваем переменную n на единицу.
И по консольному выводу мы можем увидить, что n в лямбда-выражении и внешняя переменная n фактически представляют одно и то же значение:
n inside lambda: 11 n outside lambda: 11
Для подобного лямбда-выражения компилятор будет генерировать класс наподобие:
class __Lambda1c8 { public: __Lambda1c8(int& arg1) : n(arg1) {} auto operator()() const { n++; std::cout << "n inside lambda: " << n << std::endl; } private: int& n; };
Хотя оператор (), также как и в предыдущем случае, определен как константный, но поскольку внешняя переменная сохраняется как ссылка, то мы можем через эту ссылку изменить ее значение.
В предыдущем случае мы смогли получить внешнюю переменную и изменить ее значение. Но иногда бывает необходимо изменять изменять копию переменной, которую использует лямбда-выражение, а не саму внешнюю переменную. В этом случае мы можем поставить после списка параметров ключевое слово mutable:
#include <iostream> int main() { int n{10}; auto increment = [=]() mutable { n++; // увеличиваем значение внешней переменной n std::cout << "n inside lambda: " << n << std::endl; }; increment(); std::cout << "n outside lambda: " << n << std::endl; }
Здесь внешняя переменная n передается по значению, и мы ее изменить не можем, но мы можем изменить копию этого значения, которое используется внутри лямбды, что нам и покажет консольный вывод:
n inside lambda: 11 n outside lambda: 10
По умолчанию выражения [=]
/[&]
позволяют захватить все переменные из окружения. Но также можно захватить только определенные переменные. Чтобы получить внешние переменные, применяется
выражение [&имя_переменной]
:
#include <iostream> int main() { int n{10}; // получаем внешнюю переменную n по ссылке auto increment = [&n](){ n++;}; increment(); std::cout << "n: " << n << std::endl; // n = 11 }
Если надо получить внешнюю переменную по значению, то просто указываем ее имя в квадратных скобках:
#include <iostream> int main() { int n{10}; // получаем внешнюю переменную n по значению auto increment = [n](){ std::cout << "n: " << n << std::endl;}; increment(); // n = 10 }
Если надо захватить несколько переменных, то они указываются через запятую. Если переменная передается по ссылке, то перед ее именем указывается амперсанд:
[&k, l, &m, n] // k и m - по ссылке, l и n - по значению
Можно передать все по значению и лишь некоторые по ссылке
[=, &m, &n] // все по значению, а m и n - по ссылке
Или, наоборот, передать все по ссылке и лишь некоторые по значению
[&, m, n] // все по по ссылке, а m и n - по значению
Для обращения к членам класса - переменным и функциям (вне зависимости приватным или публичным) применяется выражение [this]:
#include <iostream> void printer(auto func) { std::cout << "*******************"<< std::endl; func(); std::cout << "*******************"<< std::endl; } class Message { public: Message(const std::string& text): text{text} {} void print() { printer([this](){ std::cout << text << std::endl;}); } private: std::string text; }; int main() { Message hello{"Hello World"}; hello.print(); }
В функции print класса Message выводим на консоль текст сообщения, хранимое в переменной text. Для вывода применяется внешняя функция printer, которая осуществляет некоторое декоративное оформление и выполняет функцию, передаваемую в качестве параметра. В качестве этой функции в данном случае применяется лямбда-выражение, которое обращается к переменной text класса.
Вместе с указателем this можно захватывать и другие переменные окружения. Для этого this
можно комбинировать с & или = и
захватом отдельных переменных. Например, [=, this]
, [this, &n]
и [x, this, &n]