Передача параметров в лямбда в С++

Кажется, я пропустил какой-то момент в лямбда-механизме в С++. Вот код:

std::vector<int> vec (5);

int init = 0;
std::generate(begin(vec), end(vec), [init]() mutable { return ++init; });

for (auto item : vec) {
    std::cout << item << " ";
}
std::cout << std::endl << init << std::endl;

Если нет mutable, он не будет компилироваться, потому что я изменяю init в lambda.
Теперь, поскольку я понимаю, что lambda вызывается для каждого элемента вектора с новой новой копией init, которая равна 0. Итак, 1 нужно возвращать каждый раз. Но выход этого фрагмента кода:
1 2 3 4 5
0

Похоже, что generate захватывает копию init только один раз в начале ее выполнения. Но почему? Предполагается, что он работает так?

Ответы

Ответ 1

Теперь, поскольку я понимаю, что lambda вызывается для каждого элемента вектора с новой новой копией init, которая равна 0.

Это неверно. Лямбда - это еще один способ сделать класс и предоставить operator() для него. Часть [] лямбда описывает переменные-члены и записывается ли они по ссылке или значению. Элементом () лямбда является список параметров для operator(), а часть {} является телом этой функции. Часть mutable сообщает компилятору сделать operator() не const, как это по умолчанию const.

Итак,

[init]() mutable { return ++init; }

становится

struct compiler_generated_name
{
    int init; // we captured by value

    auto operator()() // since we used mutable this is non const
    {
        return ++init;
    }
};

Я использовал структуру для краткости ввода, но лямбда указана как тип класса, поэтому class может быть использован.

Это означает, что init является тем же самым init из последней итерации, поскольку вы только когда-либо захватываете один раз. Это важно помнить как

auto generate_lambda()
{
    int foo = 0;
    return [&foo](){ return ++foo; };
}

Покинет вас с dangling ссылкой на foo, когда функция вернется и будет использовать поведение undefined.

Ответ 2

Лямбда - это сгенерированная компилятором структура, эквивалентная:

struct lambda
{
    int init = 0; // captured value

    auto operator()() // non-const, due to `mutable`
    {
        return ++init;
    }
};

Следовательно, init фиксируется и копируется внутри лямбда только один раз - вызов лямбда несколько раз не будет захватывать init снова.

Ответ 3

Вы справляетесь и видите начальное значение init - то, что вы, вероятно, хотите сделать в соответствии с тем, что вы ожидаете, - это захват init по ссылке.....

std::vector<int> vec (5);

int init = 0;
std::generate(begin(vec), end(vec), [&init]() mutable { return ++init; });

for (auto item : vec) {
    std::cout << item << " ";
}
std::cout << std::endl << init << std::endl;

Ответ 4

Ваша ошибка здесь. "Теперь, поскольку я понимаю, что лямбда вызывается для каждого элемента вектора с новой свежей копией init, которая равна 0" (курсив мой). Нет; поскольку вы можете видеть, что лямбда полностью разделена и, следовательно, невежественна, векторного кода. Инициализация item происходит каждый раз, когда вычисляется сама лямбда-форма (в отличие от каждого момента, когда вызывается результирующее значение); здесь это означает каждый раз, когда вызывается функция generate: только один раз.