Лямбда-выражение может "захватывать" переменные, определенные вне этого выражения в окружающей области. Для этого применяются квадратные скобки, с которых начинается выражение.
Если надо получить все внешние переменные из области, где определено лямбда-выражение, по значению, то в квадратных скобках указывается символ "равно" =. Но в этом случае в лямбда-выражении значения внешних переменных изменить нельзя:
#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>
#include <string>
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]