web-dev-qa-db-fra.com

Passer des paramètres à lambda en C ++

Il me semble manquer un point dans le mécanisme lambda en C++. Voici le code:

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;

S'il n'y a pas de mutable il ne se compilerait pas car je change init dans lambda.
Maintenant, si je comprends bien, lambda est appelé pour chaque élément du vecteur avec un nouvelle nouvelle copie de init qui est 0. Donc, 1 doit être retourné à chaque fois. Mais la sortie de ce morceau de code est:
1 2 3 4 5

Il ressemble à generate capture par copie initne seule fois au début de son exécution. Mais pourquoi? Est-il censé fonctionner comme ça?

15
IgorStack

Maintenant, si je comprends bien, lambda est appelé pour chaque élément de vecteur avec une nouvelle copie fraîche d'init qui est 0.

Ce n'est pas correct. Un lambda est juste une autre façon de créer une classe et de lui fournir une operator(). La partie [] Du lambda décrit les variables membres et si elles sont capturées par référence ou par valeur. La partie () Du lambda est la liste des paramètres pour la operator() et la partie {} Est le corps de cette fonction. La partie mutable indique au compilateur de rendre la operator() non const telle qu'elle est const par défaut.

Alors

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

Devient

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

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

J'ai utilisé une structure ici pour la brièveté du typage mais un lambda est spécifié comme type de classe afin que class puisse être utilisé.

Cela signifie que le init est le même init de la dernière itération que vous n'avez jamais capturé qu'une seule fois. Il est important de se rappeler que

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

Vous laissera une référence pendant à foo lorsque la fonction revient et que son utilisation est un comportement non défini.

24
NathanOliver

Le lambda est une structure générée par le compilateur équivalente à:

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

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

Par conséquent, init est capturé et copié à l'intérieur du lambda une seule fois - appeler le lambda plusieurs fois ne capturera pas à nouveau init.

10
Vittorio Romeo

Vous faites face et voyez la valeur initiale d'init - ce que vous voulez probablement faire selon ce que vous attendez est de capturer init par référence .....

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
Soren

Votre erreur est ici "Maintenant, si je comprends bien, lambda s'appelle pour chaque élément du vecteur avec une nouvelle copie fraîche d'init qui est 0" (mes italiques). Non; comme vous pouvez le voir, le lambda est complètement séparé, et donc ignorant, du code vectoriel. L'initialisation de item a lieu chaque fois que la forme lambda elle-même est évaluée (par opposition à chaque fois que la valeur résultante est appelée); ici, cela signifie que chaque fois que la fonction generate est appelée: une seule fois.

3
Marc van Leeuwen