8

I would like to do something like this

#include <iostream>
#include <memory>

struct Foo {};

using FooPtr = std::unique_ptr<Foo>;

FooPtr makeFoo() { return FooPtr(new Foo()); }

struct Baz
{
    Baz(FooPtr foo) : Baz(std::move(foo), bar(foo)) {}
    Baz(FooPtr foo, int something) : _foo{ std::move(foo) }, _something{ something } {}
private:
    FooPtr _foo;
    int _something;

    static int bar(const FooPtr & ptr)
    {
        std::cout << "bar!" << std::endl;
        return 42;
    }
};

int main() {
    Baz baz(makeFoo());
    return 0;
}

My question is: the order of function argument evaluation is unspecified, so is it safe to pass a value that will be moved from in one argument, and the result of calling another function with the same instance, passed as reference-to-const, as the other argument?

I think the question boils down to when, precisely, the actual move operation is performed, a point on which I'm not entirely clear (especially when it comes to having optimization turned on).

wakjah
  • 4,541
  • 1
  • 18
  • 23

3 Answers3

9

The actual "moving" wouldn't occur until the move constructor of std::unique_ptr<Foo> is executed (all std::move() does is cast the const FooPtr & rvalue into a FooPtr && rvalue reference). This wouldn't occur until the two-argument Baz constructor that you're delegating to is invoked. In order for that to occur, all of the arguments to that constructor have to be evaluated first. Therefore, any use of the foo object in evaluating those arguments will happen before the actual "movement" of the unique_ptr instance.

Since you are passing the FooPtr (a.k.a std::unique_ptr<Foo> by value, and std::unique_ptr is move-only, that will trigger a move construction when evaluating the first argument to the two-argument constructor. Since the order of evaluation of arguments is unspecified, that move may or may not occur before evaluation of the second argument. Therefore, the behavior of your example is unspecified.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Jason R
  • 11,159
  • 6
  • 50
  • 81
  • 1
    I think this is incorrect because before the constructor is invoked you have to construct the passed-by-value arguments. Constructing `FooPtr foo` for the second constructor will "invalidate" `foo`. – Roman L Dec 17 '14 at 17:53
  • @RomanL: Yes, I think you're right (see the comment on the other answer by galop1n as well). – Jason R Dec 17 '14 at 17:54
  • 4
    Proof, inverting the two arguments change the result : http://coliru.stacked-crooked.com/a/37f85e8a5397eec2 (because clang most often evaluate left to right, but that's compiler dependent) – galop1n Dec 17 '14 at 17:57
  • 1
    To be clear, it's unspecified whether the call to `bar` sees an argument which is empty or not, and there is no UB. – M.M Dec 17 '14 at 21:16
  • @MattMcNabb: I'm not very good with language-lawyering, but is there a difference? I'm assuming there is some strict definition of UB that doesn't cover this, while I would think that intuitively it would be treated as such because it's unspecified whether the argument is empty or not. – Jason R Dec 17 '14 at 21:59
  • "unspecified" means there are a finite number of options but one of them must happen and the program proceeds; "undefined" means that the program has left the realm of defined-ness entirely and no future behaviour is reliable. [SO ref](http://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior) – M.M Dec 17 '14 at 22:14
4

std::move is not an operation, it's a cast to an r-value reference. The move operation will happen inside the other Baz constructor. As a result, what you are doing should work.

Nasser Al-Shawwa
  • 3,573
  • 17
  • 27
  • 1
    Without copy elision, the first argument Baz constructor perform a move construction, and this may happens before or after calling bar in the second argument. So the result is unspecified – galop1n Dec 17 '14 at 17:52
4

Scott Meyers mentioned a similar SO question in his post Should move-only types ever be passed by value?. It seems there is no definitive answer whether they should be passed by value or not, but doing so can clearly lead to an unspecified behavior, and in your case it also does.

Community
  • 1
  • 1
Roman L
  • 3,006
  • 25
  • 37