9

Example code:

#include <iostream>

int main()
{   
    std::vector<int> w(20, 123), x;
    w = std::move(w);
    std::cout << w.size() << std::endl;
}

Output on g++ 4.8.3: 0

Of course, the standard says that the move-assignment operator leaves the operand in an unspecified state. For example if the code were x = std::move(w); then we would expect w.size() to be zero.

However, is there a specified ordering or other clause that covers the self-move case? Is it unspecified whether size is 0 or 20, or something else, or undefined behaviour? Do the standard containers have any defined semantics here?

Related: this thread talks about whether you should care about self-move in your own classes, but does not discuss whether standard containers' move-assignment operators do, and doesn't provide Standard references.

NB. Is this exactly identical to w = static_cast< std::vector<int> && >(w); or does the fact that std::move is a function make a difference?

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • 2
    Interesting. Table 96 (in §23.2.1) says that `a = rv;` where `a` is a value of a container type and `rv` is a non-const rvalue of a container type, has the operational semantics that "All existing elements of `a` are either move assigned to or destroyed" and the postcondition that "`a` shall be equal to the value that `rv` had before this assignment". Hard to meet both in case of self-move-assignment. – T.C. Jul 07 '14 at 06:44
  • Question is already answered here: http://stackoverflow.com/a/13127916/2352671 – 101010 Jul 07 '14 at 06:51
  • 1
    @40two That question is itself marked "duplicate" of the question I linked which is definitely NOT a duplicate! (I guess your linked question didn't come up in my search because the search skips duplicates). Can we request that your linked question be un-marked as "duplicate" as it actually has useful discussion. – M.M Jul 07 '14 at 06:57
  • @MattMcNabb Look please at the answer. IMHO it answers your question. – 101010 Jul 07 '14 at 07:08
  • @40two The answer you link does answer my question; however I'm saying that the question of which that answer is an answer, is marked duplicate when it should not be. (Or was - it has been reopened now) – M.M Jul 07 '14 at 07:09
  • 1
    @MattMcNabb Someone reopened it. IMHO falsely closed as a duplicate. – 101010 Jul 07 '14 at 07:14

1 Answers1

7

§17.6.4.9 [res.on.arguments]:

Each of the following applies to all arguments to functions defined in the C++ standard library, unless explicitly stated otherwise.

  • [...]
  • If a function argument binds to an rvalue reference parameter, the implementation may assume that this parameter is a unique reference to this argument. [ Note: If the parameter is a generic parameter of the form T&& and an lvalue of type A is bound, the argument binds to an lvalue reference (14.8.2.1) and thus is not covered by the previous sentence. — end note ] [ Note: If a program casts an lvalue to an xvalue while passing that lvalue to a library function (e.g. by calling the function with the argument move(x)), the program is effectively asking that function to treat that lvalue as a temporary. The implementation is free to optimize away aliasing checks which might be needed if the argument was an lvalue. — end note ]

Since "the implementation may assume that this parameter is a unique reference to this argument", and self-move-assignment would violate this assumption, it has undefined behavior.

See also LWG issue 1204.

Community
  • 1
  • 1
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • That clause doesn't cover the case `v = static_cast< V&& >(v);` that I mention at the end of the question – M.M Jul 07 '14 at 06:53
  • @MattMcNabb It does - you are still passing an rvalue reference to the move assignment operator, which is a standard library function. Whether you obtained it by `move` or `static_cast` is irrelevant. – T.C. Jul 07 '14 at 06:55
  • The case `w = std::move(w);` seems to be covered by the first "Note:", unless I am misunderstand that Note? Talking about binding `w` to the parameter of `std::move`. – M.M Jul 07 '14 at 06:55
  • 1
    @MattMcNabb The answer to your last question is "yes". Your static_cast has the same effect as `std::move` in this situation. – juanchopanza Jul 07 '14 at 06:57
  • @MattMcNabb The problem is binding the result of `std::move(w)` to the argument of `vector::operator=(vector &&)`, not binding `w` to the argument of `std::move`. – T.C. Jul 07 '14 at 06:58
  • OK, so the resolution is that `t = std::move(t);` is undefined behaviour regardless of what the type of `t` is? – M.M Jul 07 '14 at 07:00
  • @MattMcNabb If `t` is a standard library type, it would appear so. – T.C. Jul 07 '14 at 07:02
  • 1
    So it's not UB if a user-defined type implements its own move-assignment operator. And if a UDT has a defaulted move-assignment operator then it depends on whether any of the member variables or base classes (which may be templated!) have any standard library types amongst them... – M.M Jul 07 '14 at 07:06
  • I assume that if an implementation can optimize away aliasing checks, then another consequence of the UB is that resources may be leaked? e.g., std::vector move-assignment ends up setting it's underlying vector memory to nullptr, without releasing the memory? This is weak. – Brett Hale Jul 07 '14 at 07:18
  • @MattMcNabb Ugh. Howard Hinnant [claims](http://stackoverflow.com/a/13129446/2756719) that `MoveAssignable` requires the type to tolerate self-move assignment, resulting in an unspecified value. That means a) the specification of `MoveAssignable` in the standard is off, as it has a postcondition that "`t` is equivalent to the value of `rv` before the assignment", which doesn't make sense if self-move-assignment leaves the object in an unspecified state; and b) either 1) no standard type is `MoveAssignable` or 2) somehow violating this assumption merely lead to unspecified behavior and not UB. – T.C. Jul 07 '14 at 07:21
  • @BrettHale I can't think of an implementation of move assignment (without checking for self-moves) that would leak in cases of self-moves. – T.C. Jul 07 '14 at 07:23
  • @TC I *think* that he is only referring to `std::swap` there. And perhaps the definition of `MoveAssignable` only applies to the case where `rv` is a variable, not a reference . (`t = t;` would be copy-assignment, not move) – M.M Jul 07 '14 at 07:27
  • @BrettHale naively one could write `{ this->p = other->p; other->p = nullptr; }` - works for all cases except self-moves – M.M Jul 07 '14 at 07:28
  • @MattMcNabb You still have to destroy whatever that's previously pointed to by `this->p`, thus you won't leak anything. – T.C. Jul 07 '14 at 07:29
  • @MattMcNabb rvalues include xvalues like `std::move(t)`, and I read HH's comments as making a general statement about `MoveAssignable`. – T.C. Jul 07 '14 at 07:31