С++ Lambdas: Разница между "изменчивым" и "захватом по ссылке"
В С++ вы можете объявить lambdas, например, следующим образом:
int x = 5;
auto a = [=]() mutable { ++x; std::cout << x << '\n'; };
auto b = [&]() { ++x; std::cout << x << '\n'; };
Оба позволяют мне изменить x, так в чем же разница?
Ответ 1
Что происходит
Первый изменит только свою собственную копию x и оставит внешний x неизменным.
Второй будет изменять внешний x.
Добавьте инструкцию печати после каждого из них:
a();
std::cout << x << "----\n";
b();
std::cout << x << '\n';
Ожидается печать:
6
5
----
6
6
Почему
Это может помочь рассмотреть эту lambda [...] expressions provide a concise way to create simple function objects (см. [expr.prim.lambda] стандарта).
У них есть [...] открытый оператор вызова встроенной функции [...], который объявлен как функция-член const, но только [...] тогда и только тогда, когда лямбда-выражения parameter-declaration-clause не являются а затем mutable (курсивный текст = кавычки из стандарта).
Вы можете думать, что
int x = 5;
auto a = [=]() mutable { ++x; std::cout << x << '\n'; };
==>
int x = 5;
class __lambda_a {
int x;
public:
__lambda_a () : x($lookup-one-outer$::x) {}
inline void operator() { ++x; std::cout << x << '\n'; }
} a;
и
auto b = [&]() { ++x; std::cout << x << '\n'; };
==>
int x = 5;
class __lambda_b {
int &x;
public:
__lambda_b() : x($lookup-one-outer$::x) {}
inline void operator() const { ++x; std::cout << x << '\n'; }
// ^^^^^
} b;
Q: Но если это функция const, почему я могу изменить x?
A: Вы меняете внешний x. Свойство lambda x является ссылкой, а операция ++x не изменяет ссылку, а ссылается на значение.
Это работает, потому что в С++ константа указателя/ссылки не изменяет консистенцию указателя/ссылки, видимого через него.