С++ 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 не изменяет ссылку, а ссылается на значение.

Это работает, потому что в С++ константа указателя/ссылки не изменяет консистенцию указателя/ссылки, видимого через него.