Search code examples
c++lambdac++14operator-precedence

Move to lambda while using the variable in the same command


Let us consider following structures designed to ruin the world.

#include <iostream>
#include <vector>

struct SkyNet {
    std::vector<int> terminators;

    SkyNet() = default;
    SkyNet(SkyNet&& that)
    {
        printf("Move construct\n");
        this->terminators = std::move(that.terminators);
    }
};

class Apocalypse {
public:
    template <typename Lambda>
    void run(Lambda lambda) {
        lambda();
    }
};

Apocalypse create_apocalypse(const SkyNet& net) {
    std::cout << "Create: " << net.terminators.size() << " terminators\n";
    return Apocalypse();
}

(Please excuse bad design of these structures and other flaws. It was created only to demonstrate specific problem.)

Then we have simple main:

int main()
{
    SkyNet net;
    net.terminators.push_back(10);

    create_apocalypse(net)
    .run([net = std::move(net)] {
        std::cout << "Lambda: " << net.terminators.size() << " terminators\n";
    });
    return 0;
}

which outputs:

Move construct
Create: 0 terminators
Lambda: 1 terminators

That means argument of the run function will be created(evaluated?) before calling create_apocalypse function on which other events are chained. Is it correct? Is this a general rule that all of arguments of an expression (not only arguments of first function) are evaluated first and only then functions get called?


Solution

  • When you create a lambda, you're creating an anonymous object. When you define the closure, you're defining its constructor and members.

    So this is equivalent to something like:

    crete_apocalypse(net).run(MyLambda{std::move(net)});
    

    Which is semantically equivalent to something like

    run(create_apocalypse(net), MyLambda{std::move(net)});
    

    Since order of evaluation for function arguments is unspecified by the standard, the implementation is free to choose. In other words, it can either choose to go for create_apocalypse(net) first and do as you seemed to expect, or it can go the other way around (which it does in your case) and construct MyLambda first, thereby moving net so that once it gets to create_apocalypse(), it's already moved.

    When it actually matters, you shouldn't rely on the behavior you get for your particular compiler. The way to avoid it and maintain correctness would be to write it like this:

    auto a = create_apocalypse(net);
    a.run([net = std::move(net)] { /* ... */ });
    

    Demo