Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moving from a moved-from object

Consider the following code.

using T = std::string;

void rotate_left(std::vector<T>& v) {
    T temp = std::move(v[0]);
    for (size_t i=0; i+1 < v.size(); ++i) {
        v[i] = std::move(v[i+1]);
    }
    v.back() = std::move(temp);
}

int main()
{
    std::vector<T> v(3);  // a vector of three Ts
    T x = std::move(v[1]);  // move-from the second element
    rotate_left(v);
    // Can we now say that v[0] is in a moved-from state, or did we
    // get undefined behavior when we moved from v[1] a second time?
}

The rotate_left function is just really simply shifting everything in the vector down by one position (and then putting the first element onto the end). My question is, does this function have defined behavior when one of the elements in the vector is in a "moved-from state"?

This is related but not quite the same as "self-move". In this case, we're moving from one moved-from object into another moved-from object, and my question is whether we can rely on this leaving both objects still in some moved-from state, or whether "assignment-from" is one of those operations that "has a precondition" and therefore can't be used on arbitrary moved-from objects.

I'm well aware that

  • this is perfectly fine for sane library types such as unique_ptr
  • this will be perfectly fine for my own user-defined types if I define them sanely
  • this will be problematic for my own user-defined types if I define them insanely
  • this is perfectly fine for all the library types I've tried on libstdc++ and libc++

So what I'm really looking for is either:

  • concrete wording from the Standard proving that this must be perfectly fine for all STL types going forward, or
  • a concrete example of an STL or Boost type "in the wild" where this code is definitely problematic

I infer from this bug that _GLIBCXX_DEBUG checks for self-move, but I can confirm that it does not check for move-from-moved; is this because they consider move-from-moved to be safe and legal, or just because nobody wrote the code to check for it yet?

like image 573
Quuxplusone Avatar asked Jun 06 '17 18:06

Quuxplusone


1 Answers

Your program's behavior is well-defined, but unspecified. The difference is that v has a valid state, you just don't know what that state is without inspecting it.

This corner of the standard has been controversial, and the wording has been difficult to get right. But I believe the latest wording (C++17) is our best attempt so far, so I will quote from that (N4660).

Disclaimer, I actually linked to N4659 as N4660 is not publicly available. The difference is inconsequential.

From 20.5.5.15 Moved-from state of library types [lib.types.movedfrom]

1 Objects of types defined in the C++ standard library may be moved from (15.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

This paragraph is a blanket statement for all types defined by the std::lib that moved-from objects are not "poison", you just don't know value they have.

Furthermore, each algorithm (including member functions) defined in the standard may have a list of preconditions that must be true prior to calling that algorithm. If there are no preconditions listed, that means that you can always call that function.

Move assignment for all types defined in the std::lib never have any preconditions listed for the left or right hand side arguments.

It is hard to quote something that doesn't exist, but that is the way this specification works.

Generalizing further, the following section refers to all types (i.e. user-supplied) that are used with the std::lib:

20.5.3.1 Template argument requirements [utility.arg.requirements]

Table 23 — MoveConstructible requirements [moveconstructible]

T u = rv;
T(rv);

rv's state is unspecified in the post-condition.

Table 25 — MoveAssignable requirements [moveassignable]

t = rv

Only if t and rv do not refer to the same object, t is equivalent to the value of rv before the assignment.

Afterwards, rv's state is unspecified (whether or not t and rv refer to the same object).

Furthermore this sections notes (notes are non-normative and often the normative text appears elsewhere) that rv must still meet the requirements of the library component (algorithm) it is being used with, even though it has been moved from.

For example, std::sort is allowed to move from a value x, and then use that x in a comparison expression. x must be LessThanComparable, whether or not x is moved-from. Only x's value is unspecified.

bool b = x < x;  // b must be false, no matter what!
like image 161
Howard Hinnant Avatar answered Sep 28 '22 02:09

Howard Hinnant