61

Let x be a variable of some type that has been previously initialized. Is the following line:

x = std::move(x)

undefined? Where is this in the standard and what does it say about it?

alfC
  • 14,261
  • 4
  • 67
  • 118
a06e
  • 18,594
  • 33
  • 93
  • 169
  • 3
    It is (user) implementation defined, it has the same issues as the traditional assignment operator. –  Jul 21 '15 at 19:03
  • 4
    What's the sense of it? – Stephan Dollberg Jul 21 '15 at 19:32
  • 3
    @πάνταῥεῖ uuum, what? This is an assignment, not initialization… clearly `x` needs to be declared first, and it doesn't *have to* be declared without initialization. So assuming OP meant to move an initialized object onto itself, if this triggers UB, it's not the lack of initialization that does so. Moving means that the object "is in an unspecified but valid state" (that's how the standard formulates it). – The Paramagnetic Croissant Jul 21 '15 at 20:20
  • 2
    @πάνταῥεῖ You can assume `x` has been initialized somewhere else. Editing to make that clear. – a06e Jul 21 '15 at 20:25
  • It isn't a "race condition". Is there a name for such read write conflict in one thread? – curiousguy Jul 29 '18 at 10:25
  • 1
    See also https://stackoverflow.com/questions/9322174/move-assignment-operator-and-if-this-rhs/ , this question is intimitely tied with the question of whether a class's move-assignment operator should cater to self-assignment. – M.M Oct 04 '18 at 22:30

3 Answers3

53

No, this is not undefined behavior, it is going to be implementation defined behavior, it will depend on how move assignment is implemented.

Relevant to this is LWG issue 2468: Self-move-assignment of library types , note this is an active issue and does not have an official proposal so this should be considered indicative rather than definitive, but it does point out the sections that are involved for the standard library and points out they currently conflict. It says:

Suppose we write

vector<string> v{"a", "b", "c", "d"};
v = move(v);

What should be the state of v be? The standard doesn't say anything specific about self-move-assignment. There's relevant text in several parts of the standard, and it's not clear how to reconcile them.

[...]

It's not clear from the text how to put these pieces together, because it's not clear which one takes precedence. Maybe 17.6.4.9 [res.on.arguments] wins (it imposes an implicit precondition that isn't mentioned in the MoveAssignable requirements, so v = move(v) is undefined), or maybe 23.2.1 [container.requirements.general] wins (it explicitly gives additional guarantees for Container::operator= beyond what's guaranteed for library functions in general, so v = move(v) is a no-op), or maybe something else.

On the existing implementations that I checked, for what it's worth, v = move(v) appeared to clear the vector; it didn't leave the vector unchanged and it didn't cause a crash.

and proposes:

Informally: change the MoveAssignable and Container requirements tables (and any other requirements tables that mention move assignment, if any) to make it explicit that x = move(x) is defined behavior and it leaves x in a valid but unspecified state. That's probably not what the standard says today, but it's probably what we intended and it's consistent with what we've told users and with what implementations actually do.

Note, for built-in types this is basically a copy, we can see from draft C++14 standard section 5.17 [expr.ass]:

In simple assignment (=), the value of the expression replaces that of the object referred to by the left operand.

which is different than the case for classes, where 5.17 says:

If the left operand is of class type, the class shall be complete. Assignment to objects of a class is defined by the copy/move assignment operator (12.8, 13.5.3).

Note, clang has a self move warning:

Log: Add a new warning, -Wself-move, to Clang.

-Wself-move is similiar to -Wself-assign. This warning is triggered when a value is attempted to be moved to itself. See r221008 for a bug that would have been caught with this warning.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • I wouldn't call the proposal in LWG 2468 anything more than that; it's just a proposal by the issue submitter and doesn't necessarily reflect the LWG's view. Under the standard as written, I would argue that the restriction in [res.on.arguments] renders self-move-assignment UB for standard library types. – T.C. Jul 21 '15 at 19:50
  • @T.C. that is fair point, I should have been more careful with how I worded that. I updated with more careful wording. – Shafik Yaghmour Jul 21 '15 at 20:12
  • 3
    "Just a proposal" is a slight understatement; Matt Austern is one of the most respected LWG members. I'm not 100% sure if he's still the LWG chair but he's been that for a _very_ long time. – MSalters Jul 21 '15 at 20:12
  • @MSalters: That's an appeal to popularity or something. Regardless, it _is_ just a proposal!! – Lightness Races in Orbit Sep 07 '15 at 18:11
  • 2
    @LightnessRacesinOrbit: It would be "appeal to authority" which happens to be a valid appeal when the authority at hand is in fact the rule-making authority. – MSalters Sep 07 '15 at 18:38
  • 1
    @MSalters: It's still not a rule until everybody else agrees it's a rule and the proposal is accepted as a rule and becomes part of an International Standard. That's the entire purpose of this process! I _would_ go with you so far as to say that it's likely this particular proposal, given the identity of its submitter, does indeed reflect general LWG view. (And no, being respected isn't "authority"! Being chair could be though.) – Lightness Races in Orbit Sep 07 '15 at 19:37
  • 1
    The clang link is broken. – a06e Feb 16 '17 at 19:12
  • [2468 moved to defect](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2468) also [active issue 2839 relevant](http://cplusplus.github.io/LWG/lwg-active.html#2839) – Shafik Yaghmour Nov 14 '17 at 22:57
  • 1
    There is a new issues on this [LWG 2839: Self-move-assignment of library types, again](https://cplusplus.github.io/LWG/issue2839) – Shafik Yaghmour May 18 '18 at 15:43
  • @becko I fixed it – Shafik Yaghmour Jul 29 '18 at 05:05
13

It will call X::operator = (X&&), so it is up to the implementation to manage this case (as it is done for X::operator = (const X&))

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • And what if I use the copy-swap idiom, where I define a unified assignment operator (capturing by value instead of const ref)? In this case I cannot check for self-assignment inside the operator implementation. – a06e Jul 21 '15 at 19:50
11

All that does is call X::operator=(X&&) (with an lvalue qualified "*this").

On primitive types, std::move does little of interest, and does not interact with = at all. So this only applies to objects of class type.

Now, for a type within std (or generated by one of its templates), objects moved from tend to be left in an unspecified (yet valid) state. This is not undefined behavior, but it isn't useful behavior.

The semantics of a each given X::operator=(X&&) would have to be examined, examining every type within std would be "too broad" for a stack overflow answer. They even may contradict themselves.

In general, when moveing from an object, you are communicating to the consumer that "you don't care what state the object is in afterwards". The use of a x = std::move(x) is thus impolite, as you (usually) do care what state x is in after the operation completes (as you are assigning to it). You are using the same object as both an lvalue and an rvalue within the same operation, and that is not good a practice.

An interesting exception is the default std::swap, which goes something like this:

template<class T>
void swap(T& lhs, T& rhs) {
  T tmp = std::move(lhs);
  lhs = std::move(rhs);
  rhs = std::move(tmp);
}

the middle line, lhs = std::move(rhs), does an x = std::move(x) if you call swap twice on the same object.

Note, however, that we do not care what state x is in after this line is completed; we have already stored the state of x in tmp, and we will restore it on the next line.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    The library thinks about `std::move(x)`, or rvalues in general, as "nobody else aliases this". I've found that a useful viewpoint. – Kerrek SB Jul 21 '15 at 19:22
  • 3
    *"The use of a `x = std::move(x)` is thus impolite"* yet it happens with the generic `swap`, possibly during some algorithm. – dyp Jul 21 '15 at 19:28
  • 1
    @dyp `template void swap( T& lhs, T& rhs ) { T tmp = std::move(lhs); lhs = std::move(rhs); rhs = std::move(tmp); }` does do an `x=std::move(x);` It does so in a context where *it does not matter what state `x` is in afterwards* however, as its state is overwritten on the next line (and "saved" on the previous line) – Yakk - Adam Nevraumont Jul 21 '15 at 19:39
  • 5
    Yes, agreed, but if self-swap is guaranteed to work, then self-move-assignment must be safe, not undefined (wrt the OP). (Essentially, this implies move-assignment has no preconditions / requirements on the argument. IMHO this and the postcondition you've mentioned are both important.) – dyp Jul 21 '15 at 19:42