Move, simply

C++ “move” semantics are simple, and unchanged since C++11. But they are still widely misunderstood, sometimes because of unclear teaching and sometimes because of a desire to view move as something else instead of what it is. This post is an attempt to shed light on that situation. Thank you to the following for their feedback on drafts of this material: Howard Hinnant (lead designer and author of move semantics), Jens Maurer, Arthur O’Dwyer, Geoffrey Romer, Bjarne Stroustrup, Andrew Sutton, Ville Voutilainen, Jonathan Wakely.

Edited to add: Formatting, added [basic.life] link, and reinstated a “stateful type” Q&A since the question was asked in comments.


Move: What it is, and how to use it

In C++, copying or moving from an object a to an object b sets b to a’s original value. The only difference is that copying from a won’t change a, but moving from a might.

To pass a named object a as an argument to a && “move” parameter (rvalue reference parameter), write std::move(a). That’s pretty much the only time you should write std::move, because C++ already uses move automatically when copying from an object it knows will never be used again, such as a temporary object or a local variable being returned or thrown from a function.

That’s it.


Advanced notes for type authors

Copying is a const operation on a, so copy construction/assignment functions should always take their parameter by const&. Move is a noexceptnon-const operation on a, so move construction/assignment functions should always be noexcept and take their parameter by (non-const) &&.

For copyable types, move is always an optimization of copy, so only explicitly write move functions for the type if copying is expensive enough to be worth optimizing. Otherwise, you’ll either get the implicitly generated move functions, or else requests to move will automatically just do a copy instead, since copy is always a valid implementation of move (it just doesn’t exercise the non-const option).

For types that are move-only (not copyable), move is C++’s closest current approximation to expressing an object that can be cheaply moved around to different memory addresses, by making at least its value cheap to move around. (Other not-yet-standard proposals to go further in this direction include ones with names like “relocatable” and “destructive move,” but those aren’t standard yet so it’s premature to talk about them.) These types are used to express objects that have unique values or uniquely own a resource.

That’s it for what advanced users need to know.





Appendix: Q&A

Wait, that seems oversimplified… for example, doesn’t C++ let me write copy functions in ways not mentioned above, like write a copy constructor that takes by non-const reference or a move constructor that can throw?

Yes, but don’t. Such things are legal but not good — ask auto_ptr (now removed), or vector implementations that used dynamic sentinel nodes (now being removed).

How can moving from an object not change its state?

For example, moving an int doesn’t change the source’s value because an int is cheap to copy, so move just does the same thing as copy. Copy is always a valid implementation of move if the type didn’t provide anything more efficient.

Can a given type document that moving from an object always changes its state? or changes it to a known state?

Yes, move is just another non-const function. Any non-const function can document when and how it changes the object’s state, including to specify a known new state as a postcondition if it wants. For example, unique_ptr‘s .release() function is guaranteed to set the object to null — just as its move functions are guaranteed to set the source object to null.

I wrote std::move(a) but a‘s value didn’t change. Why?

Because moving from an object a can modify its value, but doesn’t have to. This is the same as any other non-const operation on a.

There are other secondary reasons, but they’re all just special cases of the above fundamental reason, which applies irrespective of whether move is just a “move it if you can/want” cast or not, or whether a move vs. copy function is actually called, or other secondary reasons.

But what about the “moved-from” state, isn’t it special somehow?

No. The state of a after it has been moved from is the same as the state of a after any other non-const operation. Move is just another non-constfunction that might (or might not) change the value of the source object.

I heard that a moved-from object is in a state where “you can call its functions that have no preconditions,” or is in a “valid but unspecified state,” is that right?

Yes, both are saying the same thing as above — the object continues to be a valid object of its type, its value might or might not have been modified. The standard library specifies this guarantee for all standard types, and all well-behaved types should do the same.

Note that this is the same state as in the following example that’s familiar to C++ programmers of all experience levels:

void f( /* and optionally const */ Thing& thing ) {  // no preconditions
    // here 'thing' is a valid object of its type
    // (aka "in a valid but unspecified state")

    // ... naturally you’ll want to know its value, so now just ask it,
    //     easy peasy, just use the object ...
}

This is not a mysterious state. It’s the ordinary state any object is in when you first encounter it.

Does “but unspecified” mean the object’s invariants might not hold?

No. In C++, an object is valid (meets its invariants) for its entire lifetime, which is from the end of its construction to the start of its destruction (see [basic.life]/4). Moving from an object does not end its lifetime, only destruction does, so moving from an object does not make it invalid or not obey its invariants.

If any non-const function on an object (including moving from it) makes the object invalid, the function has a bug.

Don’t some standard types use two-phase construction where a default-constructed object isn’t in a valid state, such as unique_ptr where if it’s null you can only use a subset of its interface with defined behavior? And move-from puts them in such an invalid state?

No, and those aren’t two-phase construction types, they’re just stateful types and move-from puts them into one of their valid states.

A two-phase construction type is typically written because people are using a non-standard C++ dialect that doesn’t have exceptions to report constructor errors, so the user has to first default-construct into a not-valid (“not-fully-formed” and not yet usable) state and then additionally call a named function (which can report errors in some non-exception way) to “construct the rest of the way” before actually using the object. After that, the default-constructed state of such types is typically one you can’t get back to later via other member functions; it’s not one of the valid states, it’s an artifact. This is not recommended when using Standard C++.

None of the standard types are like that. All standard library types keep their objects in a valid usable state during their lifetimes. If they’re default constructible, the default constructor puts them into one of their valid states.

Does “but unspecified” mean the only safe operation on a moved-from object is to call its destructor?

No.

Does “but unspecified” mean the only safe operation on a moved-from object is to call its destructor or to assign it a new value?

No.

Does “but unspecified” sound scary or confusing to average programmers?

It shouldn’t, it’s just a reminder that the value might have changed, that’s all. It isn’t intended to make “moved-from” seem mysterious (it’s not).

What about objects that aren’t safe to be used normally after being moved from?

They are buggy. Here’s a recent example:

// Buggy class: Move leaves behind a null smart pointer

class IndirectInt {
    shared_ptr<int> sp = make_shared<int>(42);
public:
    // ... more functions, but using defaulted move functions
    bool operator<(const IndirectInt& rhs) const { return *sp < *rhs.sp; }
                                                // oops: unconditional deref
    // ...
};

IndirectInt i[2];
i[0] = move(i[1]); // move leaves i[1].sp == nullptr
sort(begin(i), end(i)); // undefined behavior

This is simply a buggy movable type: The default compiler-generated move can leave behind a null sp member, but operator< unconditionally dereferences sp without checking for null. There are two possibilities:

  • If operator< is right and sp is supposed to never be null, then the class has a bug in its move functions and needs to fix that by suppressing or overriding the defaulted move functions.
  • Otherwise, if the move operation is right and sp is supposed to be nullable, then operator< has a bug and needs to fix it by checking for null before dereferencing.

Either way, the class has a bug — the move functions and operator< can’t both be right, so one has to be fixed, it’s that simple.

Assuming the invariant is intended to be that sp is not null, the ideal way to fix the bug is to directly express the design intent so that the class is correct by construction. Since the problem is that we are not expressing the “not null” invariant, we should express that by construction — one way is to make the pointer member a gsl::not_null<> (see for example the Microsoft GSL implementation) which is copyable but not movable or default-constructible. Then the class is both correct by construction and simple to write:

// Corrected class: Declare intent, naturally get only copy and not move

struct IndirectInt {
    not_null<shared_ptr<int>> sp = make_shared<int>(42);
public:
    // ... more functions, but NOT using defaulted move functions
    //     which are automatically suppressed
    bool operator<(const IndirectInt& rhs) const { return *sp < *rhs.sp; }  // ok
    // ...
};

IndirectInt i[2];
i[0] = move(i[1]); // performs a copy
sort(begin(i), end(i)); // ok, no undefined behavior

There’s one more question before we leave this example…

But what about a third option, that the class intends (and documents) that you just shouldn’t call operator< on a moved-from object… that’s a hard-to-use class, but that doesn’t necessarily make it a buggy class, does it?

Yes, in my view it does make it a buggy class that shouldn’t pass code review. The fundamental point is that “moved-from” really is just another ordinary state that can be encountered anytime, and so the suggested strategy would mean every user would have to test every object they ever encounter before they compare it… which is madness.

But let’s try it out: In this most generous view of IndirectInt, let’s say that the class tries to boldly document to its heroic users that they must never try to compare moved-from objects. That’s not enough, because users won’t always know if a given object they encounter is moved-from. For example:

void f(const IndirectInt& a, const IndirectInt& b) {
    if (a < b)  // this would be a bug without first testing (somehow) that a and b both aren't moved-from
       // ...
}

Worse, it can be viral: For example, if we compose this type in a class X { Y y; IndirectInt value; Z z; /* ... */ }; and then make a vector<X> and use standard algorithms on it, some X objects’ value members can contain null pointers if an exception is thrown, so there would have to be a way to test whether each object of such a composed type can be compared.

So the only documentable advice would be to require users of IndirectInt, and by default of every other type that composes an IndirectInt, to always test an object for a null data member in some way before trying to compare it. I view that as an unreasonable burden on users of this type, nearly impossible to use correctly in practice, and something that shouldn’t pass code review.

Note that even floating point types, which are notoriously hard to use because of their NaN and signed-zero mysteries, are generally not this hard to use: With IEEE 754 non-signaling relational comparison, they support comparing any floating point values without having to first test at every call site whether comparison can be called. (With IEEE 754 signaling relational comparison, they’re as hard to use as IndirectInt. See your C++ implementation’s documentation for which kind of floating point comparison it supports.)

Does the “moved-from” state correspond to the “partially formed but not well formed” described in Elements of Programming(aka EoP)?

Not quite.

In EoP, the description of an object’s state as “partially formed but not well formed” is similar to the C++ Standard’s description of “valid but unspecified.” The difference is that EoP requires such objects to be assignable and destroyable (i.e., partially formed) while the C++ standard makes a broader statement that “operations on the object behave as specified for its type” and that a moved-from object “must still meet the requirements of the library component that is using it.” (See Cpp17MoveConstructible and Cpp17MoveAssignable.)

57 thoughts on “Move, simply

  1. @Rostislav Stelamch

    Herb talks about x-values (eXpiring values):

    “`
    my_resource calculate_resource()
    {
    my_resource val{};
    //…
    return val; // x-value
    }
    “`

    …and pr-values, (Pure Right values):

    “`
    my_resource r1 = my_resource{}; // prvalue is right side of expression (temporary)
    my_resource r2 = calculate_resource(); // prvalue is right side of expression (temporary)
    “`

    Your example is none of those 2 cases. What is more, you can not move implicitly in your example, because `t_name` potentially could be used in the `ValCpyObj` constructor body.

  2. Hello Herb,
    You said that C++ already uses move automatically when copying from an object it knows will never be used again, such as a temporary object or a local variable being returned or thrown from a function. But I did some tests/benchmarks and that’s not the case, for example here :
    http://quick-bench.com/_EVMHFf8ySWnMqtBmOR3kqHLUuA

    Why the compiler didn’t invoke move automatically?

  3. I fully agree that the argument isn’t specific to the moved-from state. `move` is just another non-const operation that might result it in the argument taking on another value, possibly one not from the original input. Comparing the results of increment, decrement, default initialization or overwriting with random bits would also be invalid and for the same reason.

    However there is one way that `move` is specific to the question at hand: it’s the non-const operation that the specification of `std::sort` requires be available. As such, in practice, the most “likely” device for “causing a comparison of unexpected values” is comparing values directly after a move-from.

    More generally, I strongly suspect that if someone were to build a static analysis tool for checking the “no operations on new values” condition, that tracking a moved-from state would hold a rather prominent place in the reasoning, even if that’s not what we actually want to describe things to each other in terms of. Just like bits, bytes and machine code hold a rather prominent place in compiled programs even if we actually want to describe thing in terms of objects, templates and functions.

  4. @Jo wrote:
    > Could this be the missing piece of the std::sort puzzle: … BUT std::sort MAY NOT invoke ‘Compare comp’ on any value that was not present in the initially supplied RandomAccessIterator range. Preconditions of std::sort also do not apply to T values outside that range.

    @Howard Hinnant replied:
    > Yes. This would not only satisfy the needs of the infamous `IndirectInt`, it would also allow us to sort sequences of floats and doubles on platforms that support nans (which we have not been able to legally do for two decades now). :-)

    Bingo. The key is that nothing about that is specific to moved-from values as in any way special (they aren’t). It also applies to incremented values; for example, std::sort should not (pathologically) compare comp(a++,b++) or comp(a-42,b-42) instead of comp(a,b) even though for numeric types not near their min/max values that should give the same result and have no side effects.

    It’s a red herring to single out a “moved-from” state as in any way special (it’s not), and trying to do that has led us astray into debates about what move maybe should mean. If we avoid that pitfall and instead talk more generally about things like restricting operations to input values, we not only start to address any actual specification issues more cleanly, but we find approaches that generalize to a broader class of problems that have nothing to do with move semantics, such as NaNs (as Howard mentions) which are not created by move-from and have nothing to do with move.

    @bcs9 wrote:
    > “Same values as in the original” describes the effect …

    Agreed, I’m not against updating the requirements and guarantees to be more lenient and stricter (respectively) as Sean argues nicely (thanks, Sean!). But…

    > … where as “do nothing after moved-from but before assigning from an original value” is more a means. Other than that, I think they are both trying to end up in the same place.

    … No, and this is important, it has nothing to do with moved-from state specifically, the moved-from state is just another valid value the object could have after any other non-const operation (or sequence thereof), such as that moving from a unique_ptr or shared_ptr ptr leaves it in the same state as calling ptr.reset() or *_ptr().swap(ptr) does (which, incidentally, starts to bring us right back to IndirectInt…).

  5. @Jo: I think your proposal is more or less another way of describing what I was attempting to propose. “Same values as in the original” describes the effect where as “do nothing after moved-from but before assigning from an original value” is more a means. Other than that, I think they are both trying to end up in the same place. I’ll happily let someone else figure out the best wording to get that effect. FWIW, I suspect your phrasing is more easily generalizable, mine however opens fewer questions about what precisely is implied by the common sense concept of “the same value”.

    (p.s. I am both @benjamin and @bcs9… different login sessions or something?)

  6. While we wait for those brave souls to return with a concrete proposal, let’s hope those who despise the holes currently in the standard take comfort in the knowledge that at least some can be filled with common sense!

  7. > Is it official?

    Not only is it not official, there’s currently no written proposal to do so. The best we have as of right now is some people have agreed to go off and write a proposal to move us in this direction. And there is no consensus on that hypothetical proposal at this time.

    As with everything, the committee will entertain proposals. Field experience counts (at least with some). And no std::sort implementation I’m aware of compares values that it has moved-from.

  8. @Howard

    Well isn’t that wonderful! Is it official? Are we getting “dependent requirements” in c++2b? “Library, please don’t surprise users by calling their callbacks with arguments they don’t expect, and that are unrelated to the task at hand”. “Users, if you give any function a callback and a set of values that may be used as its arguments, make sure the callback actually supports them!”. Wow. Duh. This is so common sense and intuitive that I had a hard time formulating it.

  9. Comment threads like this one are why I wrote this article.

    In recent weeks there was a nearly-200-message-long thread on the committee email lists, covering all the same ground. Near the end of the thread, Howard Hinnant noted the just-posted question at https://stackoverflow.com/questions/60175782/ and that three decent answers were posted within minutes, which gives hope that the programmers in the trenches are not confused. :)

    As I wrote at top: In C++, the *only* difference between copy and move is that move can change the source object’s value. “That’s it.” If we try to make that statement weaker or more complicated we no longer have a correct general statement about today’s C++ move design.

    The most common pattern I see in these threads has been trying to make C++ move be something else, rather than what it is. Yes, different move designs are possible (I mentioned relocation and destructive move) and I’m very interested in them! But they are different designs.

    A few selective responses:

    @Sean said (combining from different responses, hopefully accurately):
    > I do want the standard to change the guarantees going forward… / “unspecified” – is without meaning unless you state what operations MUST NOT have preconditions… / The “must still meet the requirements of the library component that is using it” footnote is just weasel-words…

    Last week in Prague we reviewed whether the standard library should be more lenient about types like IndirectInt that are “poorly written” (the view of multiple experts in the room) and we decided to pursue relaxing some requirements on user types, but not in a way that’s unique to a moved-from state as though that were somehow special (it’s not). However, note that this leniency does not endorse such user types (many of us continue to view them as poorly written), and it does not affect standard types which continue to be required to be “well written” and have a fully valid state after move (that you typically can get into via other non-const operations, move-from being just a non-const operation).

    @Andrey:
    > I agree here with Sean that the best strategy is to assume that moved from object may not be holding its invariant but rather be in a special state when only destruction or destruction and assignment to are allowed operations.

    I understand some people argue for this, which is why I wrote the Q&A questions that mention “invariants” and “destructor.” It is possible to try to write a type like that, but it violates http://eel.is/c++draft/basic.life — and currently also http://eel.is/c++draft/defns.valid , though as I mentioned we are looking at making the standard library more lenient to such (erm, ‘problematic’) types — but not endorsing them, and not taking any such leniency for any standard types.

  10. > Could this be the missing piece of the std::sort puzzle:

    Yes. This would not only satisfy the needs of the infamous `IndirectInt`, it would also allow us to sort sequences of floats and doubles on platforms that support nans (which we have not been able to legally do for two decades now). :-)

  11. Could this be the missing piece of the std::sort puzzle: std::sort should be allowed to do whatever it wants with the values in the provided iterator range, and that includes fiddling with moved-from objects in whatever way tickles the implementor’s fancy, within the boundaries of defined behaviour BUT std::sort MAY NOT invoke ‘Compare comp’ on any value that was not present in the initially supplied RandomAccessIterator range. Preconditions of std::sort also do not apply to T values outside that range.

    For example, assuming the values initially in the supplied range are in a state such that ‘comp(*it_a, *it_b)’ is valid for all RandomAccessIterators it_a and it_b of that range such that it_a != it_b, this added requirement effectively prevents std::sort from misusing the possibilities offered by the lax requirements on T and RandomAccessIterator (and thus the “valid but unspecified” state). Only the user of std::sort can be responsible for invalid ‘comp(a, b)’ expressions. This requirement also leaves the door open to using std::sort on a range where values may be in that famous valid but unspecified state, provided Compare comp is total over the set of valid (specified or not) T objects.

    The above prevents std::sort from messing up, this is where we tell users not to mess up:

    The standard should also explicitly require that the values in the supplied range must be in the domain of ‘Compare comp’ (for any pair of RandomAccessIterators it_a and it_b in the range such that it_a != it_b, the expression ‘comp(*it_a, *it_b)’ must be valid). This would bridge the gap that currently prevents one from proving, using the standard only, that the comparison expressions will indeed be valid (as already required by the standard) in cases such as @Sean’s 2-line example. This way we don’t have to resort to specifying in which order operations may be carried out, and at which points in that sequence of operations Compare comp may be invoked, as suggested by @benjamin and discussed by @bcs9.

    I think this also allows the T objects belonging to Compare comp’s domain (a potentially strict subset of the T type) to satisfy that much decried footnote: “Note: rv must still meet the requirements of the library component that is using it. The operations listed in those requirements must work as specified whether rv has been moved from or not”. Indeed, std::sort may only invoke Compare comp on values that were originally in the iterator range, so if the value of the hypothetical moved-from T is one of those, we know it still satisfies the requirements by construction. If not, the requirements never applied to it in the first place.

    It seems to me that this constitutes a small departure from the usual form requirements take (see 16.4.1.3/4 [library.description.structure.requirements]) as this one would not concern itself with all the values of a type but rather with a subset of that type that only exists at runtime.

    A type theorist could pretty much waltz into my room at this point and say “congrats Jo you just reinvented dependent types, only 70 years late”.

  12. @Yankes: How about an alternative phrasing “After moving-from a value, the next operation must be destruction or some form of ‘overwrite'”? (Maybe with an exception, along the lines of as-if, for other zero-precondition operations that are “not observable”? E.g. checking the capacity of a string?)

  13. The “requirements of the library component [sort] that is using it” are, among others, that another value can be assigned to a moved-from value and that, *after that is done*, “comparison imposes a strict-weak order”.

    Your agreement seems to either assume that the “unspecified” result of a move-from is a permanent state (at which point there is no useful result that can be had by assigning to it, so why even require that?) or that an algorithm must be allowed to apply any operation it is permitted to do, in any arbitrary order (which seems to be a major, and majorly unnecessary, restriction on the implementation of algorithm and data types alike).

    You could argue that things like that ordering constraint should be explicit, but adding such notes everywhere would be redundant (they are already implicit by construction), verbose (there are likely to be way more constraints than just move semantics) and unnecessary (common sense also requires them, even if you don’t put together the details).

  14. I partially agree with Sean Parent, main reason is that moveout object have meaningless value in it.
    Form code perspective it value is valid and I can examine it (like counting number of `A` in moved string).
    But from domain perspective (or business logic) this value is useless.

    service.user = "sys";
    service.password = std::move(password));
    

    There is any sane thing I could do with `password` after this line? I can check it `std::size` but what logic I could with that result? I can’t send this string to UI because it could leak sensitive data but I can’t use it again as password because it can have some unrelated value. How I could distinguish between this two cases?
    This is simply impossible. This is still valid string (and I think this should stay that way) but from whole program perspective this value is poisoned. This mean any generic algorithm that work with movedout values should avoid any operations aside form assignment, swap and destroying it.

    And this should be applied to any std algorithm, if it generate moveout object it should “track” them and do not access them again outside of assigning them new value. Best example of this is `remove` from `std::vector`, it create range of values that have meaningless values and should be reseted or erased (what would be point of `std::sort` on whole vector after that?).

    Changing definition of move operation is not needed in my opinion. Adding precondition on every operation that “not movedout” is still useful concept but it still is inline with “valid but unspecified”, this is similar to `valueless_by_exception`.

  15. > Then my two-line example is “implementation-defined”? or UB?
    > If my two-line example is not guaranteed to be correct, the standard is defective.

    Of course the standard has defects (http://cplusplus.github.io/LWG/lwg-active.html#submit_issue). Although I’m not sure how to write wording that says: If Sean writes it, it’s correct. :-)

    I think it important to distinguish between what the standard says and what we would like it to say.

    And although I would support relaxing requirements on std::sort to restrict when it can compare values, I would not support settling on EOP’s “partially formed” as a model for move semantics across the board.

  16. > A conforming std::sort algorithm could conceivably compare an element with itself and expect false. In such an algorithm, said comparison might even be on a moved-from value. A high-quality implementation wouldn’t do this as it is wasteful to perform an operation you already know the answer to. But it’s a QOI issue, not a conformance or correctness issue.

    Then my two-line example is “implementation-defined”? or UB?
    If my two-line example is not guaranteed to be correct, the standard is defective.

  17. @bcs9

    > I could believe there is an issue with the wording in the standard for sort. But I don’t see any issues in what you cited about move construction.

    The requirements stated in the standard fo the postconditions of move are here http://eel.is/c++draft/utility.requirements#tab:cpp17.moveconstructible and http://eel.is/c++draft/utility.arg.requirements#tab:cpp17.moveassignable. Those are the requirements for any (moveable) value within the standard library. Sort relies on movable. I know you asked for me to cite all the operations – that is just a PITA because you have to go through all the requirements for iterator and comparisons and pull out all the required ops.

    > As such, under the current definition of move semantic, there is never a point in which a conforming std::sort is permitted to attempt a comparison when the result is not fully defined.

    I’m not sure where “permitted” comes from but I can see that argument. By that argument, the only “fully-defined” operations would be assignment-to and destruction, since anything reading from an unspecified value would not be “fully-defined”. i.e. we can only write-to the object, or destroy it.

    But I do not think that “rv must still meet the requirements of the library component that is using it. The operations listed in those requirements must work as specified whether rv has been moved from or not.” says that. Specifically “those requirements must work as specified whether rv has been moved from or not” – I’m not sure how you get from there to something like “but comparison no longer has to impose a strict-weak order on rv because it really would be pointless to compare an unspecified value.” It is true – but doesn’t follow.

    Sean

  18. A conforming std::sort algorithm could conceivably compare an element with itself and expect false. In such an algorithm, said comparison might even be on a moved-from value. A high-quality implementation wouldn’t do this as it is wasteful to perform an operation you already know the answer to. But it’s a QOI issue, not a conformance or correctness issue.

  19. @Sean: your two-line example is unconditionally correct. Any std::sort implementation that would exhibit any problems is *it self* wrong. (And the same is true of your indirect_int example for the exact same reason.)

    FWIW I agree with you that both examples do and must work. Where I diverge from you is that, by my reading, the the standard in its current form *already* requires that both work. This is because a] after moving from an object nothing is assured about the state of the object other than it being “valid” (and as such comparing it with anything would be pointless and unspecified) and b] after move assigning to, an object it “is equivalent to the value of [the source] before the assignment” (and as such the comparison is again valid). As such, under the current definition of move semantic, there is never a point in which a conforming std::sort is permitted to attempt a comparison when the result is not fully defined.

    … Almost certainly. But if there are any issues here (and I think there is not), it’s with it being underspecified what std::sort is allowed to do and in what sequence, not with regards to the post condition of a moves in general.

  20. A correction – my 3) above “copy-move-assignment” was intended to be “move-construction” (it was late, sigh).

    > In combination, my reading is that as long as sort never inspects an element between a move from (at which point some stuff begins being un-specified) and a subsequent move-to (at which point it ends being un-specified and is again specified) then everything is *already* just fine. That’s a library implementation detail, not a standards wording detail.

    So my two-line example is or is not correct depending on the quality of the library implementation? That would mean that Herb’s first example is not UB, but implementation-defined? I’m not sure I get your point here.

    Here is a slightly simpler version of the same example:

    unique_ptr<int> a[]{make_unique<int>(1), make_unique<int>(0)};
    sort(begin(a), end(a), [](const auto& a, const auto& b) { return *a < *b; });
    

    Let’s loop this back to @Herb’s example. Here is a simplified form of his first IndirectInt example. I rewrote it to match my example above but did not make any other substantial changes:

    class indirect_int {
        unique_ptr<int> _ptr;
    
    public:
        explicit indirect_int(int x) : _ptr{make_unique<int>(x)} {}
        friend inline bool operator<(const indirect_int& a, const indirect_int& b) {
            assert((a._ptr && b._ptr) && "FATAL: Use after move.");
            return *a._ptr < *b._ptr;
        }
    };
    
    int main() {
        indirect_int a[]{indirect_int(1), indirect_int(0)};
        sort(begin(a), end(a)); // (*)
    }
    

    According to @Herb (and my reading of the current standard), this (*) call to sort is UB. This example is equivalent to my prior two-line example. Not only does this code work – it must work. The current standard wording is wrong. It conflicts with common use, with every library implementation, and is self-contradictory and not satisfiable.

    Herb’s argument above is wrong (sorry Herb – I have a huge amount of respect for you and your work!). The continued attempts to try and satisfy the contradictory wording in the current standard is damaging, not just to the standard library but to all code that uses the standard library. This article is a testament to that.

    A move operation isn’t mysterious or magical but it is not so simple, just as copy is not simple. Move is a unique basis operation, distinct from copy-construction and copy-assignment, and improves the efficient-basis for the type system.

  21. @Sean: I understand your argument. I think it is wrong.

    I could believe there is an issue with the wording in the standard for sort. But I don’t see any issues in what you cited about move construction. (That said, I suspect that assignment and destruction are the only operations that can *generically* be assumed to work immediately after a move-from).

    Do you have a citation for that list of 5 requirements that *sort* imposes?

    I suspect what is going on is that that the requirements imposed by *sort* are, or should, be a bit more nuanced than “at all times and regardless of the order of operations, the following operations must be valid”. More like something about some of them holding for the underlying values regardless of how those values get copied/moved about. But even that may not be needed:

    https://eel.is/c++draft/sort#2
    > Preconditions: […] RandomAccessIterator meets the Cpp17ValueSwappable requirements and the type of *first meets the Cpp17MoveConstructible (Table 26) and Cpp17MoveAssignable (Table 28) requirements.

    Looking up Cpp17MoveAssignable:

    https://eel.is/c++draft/utility.arg.requirements#tab:cpp17.moveassignable
    > If t and rv do not refer to the same object, t is equivalent to the value of rv before the assignment

    In combination, my reading is that as long as sort never inspects an element between a move from (at which point some stuff begins being un-specified) and a subsequent move-to (at which point it ends being un-specified and is again specified) then everything is *already* just fine. That’s a library implementation detail, not a standards wording detail.

    At this point, I’m satisfied that there is not an issue here.

  22. @Andrey

    I stand corrected on most points. I’m curious to know what the changes you suggest would end up looking like.

    @Sean

    I’m not convinced. The standard wouldn’t go to the trouble of specifying that ‘comp(a, b)’ must be valid and return a bool, and that comp must implement a strict weak ordering, yadda yadda, if std::sort was allowed to wreak havoc, move objects, and pass objects *it* has moved from to it.

    Thank you for the entertaining discussion.

  23. > Both cases should be considered as undefined behavior and ideally the compiler should spit an error or warning about it.

    Not like this actually. First case is a malformed program.

    For the second case I’d like to have an attribute on move constructor and move assignment saying that the moved-from object is left in either partially-formed state or destroyed (i.e. it a destructive move). improper use of such moved from object should be treated as malformed program so compiler can warn about it.

  24. @Jo

    > how does it help you, concretely, for the standard to define those special states where only assigning and destructing are allowed? Say you have one of those moved-from File objects in that special state. What prevents you from still calling a disallowed member function? And what should happen if you do call it? Should you get a compile error? If not, then what?

    Many things in C++ are not hardly prevented and described as a precondition (that developer has to ensure by itself) with undefined behavior if the precondition is breached (usually backed up by debug assertion). Right now you can call a destructor on any object and then use it on the next line. That’s not very different from moving from and then use the object it like it’s well-formed. Both cases should be considered as undefined behavior and ideally the compiler should spit an error or warning about it.

    > For correctness purposes, the requirement that programs only destruct or assign is only practical if it’s statically enforceable. Either that or I’m failing to conceive how else this requirement can help. For optimization purposes it does help, but then safety is thrown out the window, isn’t it?

    Then C++ should not exist. There is the difference between C++ and let’s say Rust: in C++ many things are UB for the sake of performance usually) and it is developer’s responsibility to keep it correct. There are also compiler and static analyzer to help developers with keeping things correct. But this first require a way to tell tools what is correct behavior and what is not. This is either generalized in the standard or specified by developer via attributes.

    My example is not about optimization but about having nice simple types without valid/null invariant that make usage more hard and verbose.

    Btw, the Herb’s taking on the item is not statically enforced either.

    > With your argument as well as with @Sean’s, there is no concrete, pragmatic suggestion of a technical solution to achieve your goals.

    Compiler warning/error is the suggestion. And it’s feasible.

    > This is quite hand-wavy. Are you suggesting that the standard should provide for the needs of programs that are not compilers? Or that compilers should now embark the static analysis features typically the preserve of third-party tools such as valgrind or asan? I think that’s an unrealistic expectation.

    Neither ASAN nor Valgrind are static analyzers, they’re runtime tools or runtime analyzers if you want. And clang static analyzer is a part of the standard clang toolset and is more like the compiler extension.

    The standard should allow me to express my intentions so ideally compiler can verify whether my program is correct or not. Saying that moved from object is partially formed in the standard will allow compiler and other tools to reason about the code around move and detect bugs there.

  25. > @Sean: Where is the assertion coming from that std::sort requires the moved-from state be comparable?

    It comes specifically from here https://eel.is/c++draft/library#tab:cpp17.moveconstructible as well as the immediately following section on MoveAssignable –

    > rv’s state is unspecified [ Note: rv must still meet the requirements of the library component that is using it. The operations listed in those requirements must work as specified whether rv has been moved from or not. — end note ]

    The “valid but unspecified” terminology is used in the iterator requirements for indirect-move which reference the post-conditions for moveable.

    Sort has requirements around the following operations on values (splitting out the requirements or lh and rh operands):

    1) move-assignment-to
    2) move-assignment-from
    3) copy-move-assignment
    4) destructible
    5) comparable

    The standard states the value post-move must satisfy all of these requirements. Any implementation will only rely on (1) and (4) for a post-move object. The same is true for _every_ algorithm and container in the standard library (modulo aliasing issues, which may add a special case to (2)).

    However, there is no place in the standard that states this. There is no possible way for an unspecified value to satisfy the requirements as stated unless you also require that all operations are total, including comparisons (and even then it is questionable).

    This “valid but unspecified” phrasing is used as justification for not having `noexcept` move constructors for any of the node-based containers. There is no such actual requirement in any library implementation (nor could there be).

    What table 26 should say for the postcondition is something like:

    > rv is assignable-to and destructible. [ Note: rv’s state is otherwise unspecified. ]

    With similar phrasing for all the other “valid but unspecified”, “unspecified”, and “must still meet…” instances.

  26. @Sean: Where is the assertion coming from that std::sort requires the moved-from state be comparable? While I could envision a sort that moves from a value and then as the next thing compares with it (i.e. it treats move like copy) it would clearly be a buggy implementation. What I would expect the sort specification would actually require is that after moving from *and then (move) assigning to* a value it be comparable (and presumably compare the same as would the value that it was assigned from).

  27. @Jo

    > The standard says your two-line example is correct, however, since it’s obvious the expressions on that cppreference page will be valid.

    No, the standard does not say my two-line example is correct. The standard states that all the requirements of the algorithm must be satisfied by the moved from-state of my object. It does not say “except for comparison”. I’m not talking about the case where the elements were moved from before the call to sort – that is why I made it explicit with the two-line example. The standard has been incorrect since C++11. It is precisely because the comparison is not required to be total that an unspecified value cannot satisfy the requirements (or in this case, even a specified empty value). I wouldn’t care much at all about it except for the amount of damage “valid but unspecified” has done in the rest of the standard.

    > “An object can be valid and not destructible”

    https://en.cppreference.com/w/cpp/thread/thread/~thread can call terminate as one example from the standard library. I don’t know of any place in the standard that specifies that any operation must not have a precondition.

    > Yes! We agree. But I still believe the “valid but unspecified” is worth it.

    And you are free to write that in your own coding guidelines. However, the standard should not be trading off efficiency for an illusion of safety.

    > Just don’t use move semantics or the standard library features that you feel are crippled in those cases.

    Well, that means not using any of the node-based containers or variant… and not supporting them in any of my libraries. But sure…

  28. @Sean

    “A moved-from unique_ptr does not satisfy the requirement of being comparable and so does not satisfy the requirements of sort”

    From the standard (16.4.1.3 as of today): “Requirements are stated in terms of well-defined expressions that define valid terms of the types that meet the requirements. […] Required operations of any concept defined in this document need not be total functions; that is, some arguments to a required operation may result in the required semantics failing to be met. [ Example: The required < operator of the totally_­ordered concept ([concept.totallyordered]) does not meet the semantic requirements of that concept when operating on NaNs. — end example ] This does not affect whether a type models the concept."

    So technically, it is your use of std::sort with moved-from unique_ptrs that does not satisfy the requirements of the standard because it involves the potentially invalid expressions *a and *b, assuming it's possible that unique_ptrs are moved before the std::sort call. std::sort itself, moved-from unique_ptrs and the "valid but unspecified" requirement have nothing to do with this. This was true even before C++11. The standard says your two-line example is correct, however, since it's obvious the expressions on that cppreference page will be valid.

    Most of the rest of your post just loses me completely.

    "An object can be valid and not destructible"

    I didn't know that. I'd be surprised (and enlightened!) if it were true. I thought all objects were destructible at any point during their lifetime, with no preconditions. I don't even care to check. If such a situation were possible, I doubt it would ever happen in any sane program.

    "and valid but not assignable"

    Easy answer given the framework I've used so far to understand the notion of "validity": if the assignment operator has no preconditions, then it's fine to use it even when all you know is that the object is in a "valid" state. If the assignment operator has preconditions on the state of the assigned-to object (I didn't even know that was conceivable…) then "valid" doesn't cut it. So nothing new under the sun.

    "Any given type, in the standard, or provided by the user, can provide a stronger guarantee for the moved-from postcondition. […] However, providing a strong guarantee often requires damaging tradeoffs"

    Yes! We agree. But I still believe the "valid but unspecified" is worth it. For all the cases where it's a burden, you can revert to more barebones usage of the language. Just don't use move semantics or the standard library features that you feel are crippled in those cases. I don't think the solution is to have the standard relax requirements that are down-to-earth and safe for most use cases. It's like data structures that are perfectly reasonable but absent from the standard library like "local_vector", a vector on the stack: just roll your own and have it be what you want.

    @Andrey

    "Sorry but you did not get my point. My point was not about catching this errors but about being able to use classes like “opened file” that does not have “null” state."

    I believe I did get your point: how does it help you, concretely, for the standard to define those special states where only assigning and destructing are allowed? Say you have one of those moved-from File objects in that special state. What prevents you from still calling a disallowed member function? And what should happen if you do call it? Should you get a compile error? If not, then what?

    For correctness purposes, the requirement that programs only destruct or assign is only practical if it's statically enforceable. Either that or I'm failing to conceive how else this requirement can help. For optimization purposes it does help, but then safety is thrown out the window, isn't it?

    With your argument as well as with @Sean's, there is no concrete, pragmatic suggestion of a technical solution to achieve your goals.

    "We can also ahelp compiler/static analyzer […] by adding new attributes or by introducing a destructive move"

    This is quite hand-wavy. Are you suggesting that the standard should provide for the needs of programs that are not compilers? Or that compilers should now embark the static analysis features typically the preserve of third-party tools such as valgrind or asan? I think that's an unrealistic expectation.

  29. @Jo

    Sorry but you did not get my point. My point was not about catching this errors but about being able to use classes like “opened file” that does not have “null” state.

    Also your statement although possibly theoretically correct (honestly I don’t want to go into all this long discussions about C++ and Rust type systems and so on) but practically if not C++ compiler then static analyzer would be pretty good at finding bugs of use-after-move-from. Currently it’s already doing a good job at finding use-after-free bugs and use-after-move-from is a very similar case. We can also ahelp compiler/static analyzer to know whether the moved-from state of a type is well formed or partially formed by adding new attributes or by introducing a destructive move.

  30. @Jo

    Here is a complete example:

    vector<unique_ptr<int>> v{ make_unique(1), make_unique(0) };
    sort(begin(v), end(v), [](const auto& a, const auto& b){ return *a < *b; });
    

    According to the current standard, this code is not required to work. Sort is allowed to use move-constructors and move-assignment, (specifically defined by indirectly_moveable in the _common algorithm requirements_ of the current draft standard) and the required postcondition of those move operations is that the moved-from object be “valid but unspecified” and “must still meet the requirements of the library component that is using it.” A moved-from unique_ptr does not satisfy the requirement of being comparable and so does not satisfy the requirements of sort. The comparison function provided does meet the requirement of a strict-weak ordering for all values in the range.

    Of course, this code does in actuality work, so clearly std::sort() doesn’t actually require that the moved-from object satisfy the general requirements of the objects passed to std::sort(). So what does `std::sort()` require of the moved-from object? It requires that the moved-from object be assignable-to, and destructible. That is all it requires. That is all every algorithm and container requires in all of the standard library for a moved-from object. But the standard never states that as a requirement. It provides the requirement that the value be “valid” (satisfies the object invariants) but that doesn’t tell you what operations can be performed. An object can be valid and not destructible, and valid but not assignable. And in a footnote, “must still meet the requirements of the library component that is using it” but that is not achievable. Further, the compiler cannot check for use-after-move in an algorithm constrained by the indirectly_moveable concept because indirectly_moveable doesn’t impose such a constraint.

    Any given type, in the standard, or provided by the user, can provide a stronger guarantee for the moved-from postcondition (or weaker, if it is not intended to be used with the standard library). However, providing a strong guarantee often requires damaging tradeoffs, such as move-constructors that are not noexcept for std::list and other node types – they do not guarantee noexcept because they may perform a heap allocation on move to maintain a “valid” state – an expensive operation which is more than likely going to be deallocated before it is ever read. Trying to provide a stronger guarantee has also led to the situation where std::variant cannot provide the guarantee that it holds a value – so it is effectively an optional-variant. These choices actually make it more difficult to reason about code and to implement useful properties such as strong exception guarantees.

    So to answer the question, “what should the standard say?” Very simple. In the requirements sections for the moveable concepts get rid of “valid but unspecified” and state, “the moved from type must be destructible and assignable-to”, and remove the footnote about “must still meet the requirements of the library component” which since the requirement is “destructible and assignable-to”.

    Finally, stop doing more damage for new types by trying to satisfy this “valid but unspecified” state for a moved-from object. If stronger guarantees fall out of the implementation and other requirements (such as the guarantee that a moved-from vector is empty), then fine. But don’t otherwise compromise performance or guarantees to satisfy “valid but unspecified” nonsense.

    Then maybe we can move on to a discussion about aliasing…

  31. @Sean

    I understand the term “requirement” as any property described by the standard that a program has to have for it to avoid UB or undesirable but defined behavior (e.g. “if you do such or such, std::terminate will be called”).

    My understanding was that an iterator or pointer being invalidated does mean something different than an object being invalid in the sense I (and Herb?) have been using so far, which was “satisfies invariants, may be used as you normally would”. So I’d be surprised if the standard meant by “invalidated iterator” the same thing Herb means when he says “invalid object” in this post, for example what you get when you move from an IndirectInt. It seems there is an unfortunate terminology collision at play here.

    “But you haven’t told me what operations are required to have no preconditions”

    As I said previously, there is no fundamental difference between the moved-from state and any other valid but unspecified state, such as when you are given a std::string&. So the operations that have no preconditions after a move are the same that have no preconditions before it. I keep repeating myself so I think we’re having trouble communicating here…

    “What can sort() do with a moved-from value?”

    In your example, std::sort is not the issue, your lambda is! The fact that you would have it perform operations with preconditions on objects in an unspecified state is the issue. Then again, there is nothing here that is specific to move semantics: your program would have the same problem in C++03 if there was a function “void mess_with_my_unique_ptr(unique_ptr&)” that you called on the unique_ptrs before the sort, and you didn’t know what it did. You are complaining to the standard for an issue that is unrelated to move-semantics and entirely in the hands of developers whose job it is to control the state of their objects and the pre- and post-conditions of their operations.

    Let’s assume for a second that the standardization committee decided to follow your advice and to provide stronger requirements for moved-from objects. What would those even look like? Genuinely curious here, I just can’t see how that wouldn’t lead to a hellscape of crazy hoops to jump through for implementors of move operations.

    “You get a value which is precisely unspecified – reading that value has as much meaning as reading an uninitialized integer”

    Yeah but I don’t get why you’re so fixated on the meaning of the value. It just doesn’t matter for any practical purpose that the value has or doesn’t have meaning. Validity-as-in-satisfies-invariants is enough for all practical purposes, destruction being chief among them. Correctness issues are the developer’s problem.

    And here’s a valid but contrived piece of code that reuses a moved-from object:

    #include <iostream>
    #include <vector>
    #include <memory>
    #include <string>
    
    struct Cake
    {
        std::string                name;
        std::unique_ptr<long long> calories;
        std::vector<std::string>   ingredients;
    };
    
    long long total_calories = 0;
    
    void eat_cake(Cake cake)
    {
        total_calories += *cake.calories;
        std::cout << "om nom nom\n";
    }
    
    void bake_cakes_non_stop(unsigned count)
    {
        Cake scratch_cake;
    
        while (count-- > 0)
        {
            // None of these calls have preconditions
            scratch_cake.name = "Apple pie";
            scratch_cake.calories.reset(new long long(9000));
            scratch_cake.ingredients.assign({ "sugar", "flour", "egg", "sugar" });
    
            eat_cake(std::move(scratch_cake));
        }
    }
    
    int main()
    {
        bake_cakes_non_stop(42);
        std::cout << "Ate " << total_calories << " calories" << std::endl;
    }
    

    I can see this kind of code being used in a serious production system.

    @Andrey
    @ricab

    I think you want to have the compiler prove at compile-time that you are safely using your File and that it hasn’t been made invalid when you use it, and for that you need a much more powerful type system than C++ is equipped with. Having the standard merely state that all operations on moved-from objects other than assignment and destruction are prohibited won’t help compilers determine whether that requirement is obeyed by the program. You need real type theory tools for that, or a custom, problem-specific proof-checker like Rust has.

  32. I’d also like to see @Andrey Upadyshev’s point debated. It seems to me that this take on moving breaks RAII. That is the corollary in @Jo’s suggestion that a File type can either be movable or guarantee it always holds a resource. With such movable objects, the life cycle of the resource is no longer tied to the life cycle of the object.

    I view this as a critical point. I tried to raise it here – https://github.com/isocpp/CppCoreGuidelines/issues/231#issuecomment-371888948 – but I have not seen it properly refuted or acknowledged.

    IIUC this intersects with @Sean Parent’s point if we generalize resource validity to just validity (?)

  33. > To pass a named object a as an argument to a && “move” parameter (rvalue reference parameter), write std::move(a). That’s pretty much the only time you should write std::move,

    I think that it is also appropriate to use move when passing a named object as an argument to a *value* parameter (“T param” not just “T &&param”). If move is not used, a copy will be made (as far as observable program behavior is concerned).

    See this demonstration:
    http://coliru.stacked-crooked.com/a/c62347956fb2263a

    Changing the code to use `func2(std::move(a))` results in the move constructor being used for the argument of func2 instead of the copy constructor.

  34. @Jo

    That’s what I’m saying. Following Herb’s logic I can not implement a movable class that does not have a null or invalid state in its invariant. That’s pretty limiting. I agree here with Sean that the best strategy is to assume that moved from object may not be holding its invariant but rather be in a special state when only destruction or destruction and assignment to are allowed operations.

  35. > Move is a noexcept non-const operation on a, so move construction/assignment functions should always be noexcept and take their parameter by (non-const) &&

    What went wrong when std::unordered_map’s move constructor was being specified?

  36. > I fail to see how “calling front on an empty container is UB” expresses a guarantee concerning std::vector::front and not a requirement. Postconditions, on the other hand, would be guarantees. As in something like “if you call std::vector::reserve with an argument of N then the capacity of the vector is increased to at least N.”

    To keep the terminology straight – when talking about requirements I’m talking about the requirements on a type and values to satisfy an algorithm, container, or other parameterized type. This included both the signature and the semantics.

    > I don’t know what this “meaning” is that you’re talking about.

    A value has meaning in so much as it corresponds to an abstract or concrete entity. A value which is unspecified, cannot have any such correspondence, and so has no meaning. Yes, you can inspect the attributes of the type, but that does not determine any meaning. The value is still meaningless. When we talk about a pointer or iterator being valid or invalid this is exactly what we are referring to. Validity is not the same as “satisfies the invariants” – an invalid iterator still satisfies the invariant of an iterator. The precondition of many operations on an iterator are “must be valid”. “Unspecified” is invalid, without meaning. So “valid but unspecified’ is a three-word contradiction.

    > There is a requirement, and it is the “validity” requirement, where “valid” means “satisfies invariants”. From which you can deduce that all operations with no preconditions are valid in turn.

    But you haven’t told me what operations are required to have no preconditions. What can sort() do with a moved-from value? Is destruction of a type used with sort to have no preconditions? The standard does not say so.

    > Regarding the unique_ptrs in your example, if it is possible for them get moved from at some point before the std::sort call, then you have to discover their state before using operator*!

    You miss the point. Assuming my vector contains only non-empty unique-ptrs (which would be required to satisfy the precondition of std::sort – because all values must be comparable), according to the standard this still is not valid, because maybe std::sort wants to call my comparison function with a moved-from object.

    > I’m starting to think you underestimate “moved-from objects must satisfy invariants” as a perfectly reasonable requirement on move operations.

    It is not a requirement unless you tell me _which_ operations must have no preconditions. As a guarantee, it imposes unnecessary restrictions that cause code to be less efficient and more error-prone.

    > Surely for optimization purposes more guarantees would help, but at what cost to implementors and users?

    I want less guarantees for optimization and stronger requirements for correctness.

    > All I meant was that in practice you don’t get unusable garbage, not that all moved-from strings are empty. If your implementation of std::string swaps the contents then, of course, you may end up with your moved-from string being non-empty.

    You get a value which is precisely unspecified – reading that value has as much meaning as reading an uninitialized integer (none). Sure the bit pattern satisfies the invariant of the string, but just as you don’t want to read the bits of an uninitialized integer, there is no reason to read the bits of an unspecified string. But any particular type can have whatever guarantees make sense. For the node-based containers (list, set, etc.) trying to make stronger guarantees removes an important basis operation (noexept move construction) for zero benefits. I view that as damaging.

  37. @Sean

    “That is a guarantee, not a requirement. Different things.”

    I fail to see how “calling front on an empty container is UB” expresses a guarantee concerning std::vector::front and not a requirement. Postconditions, on the other hand, would be guarantees. As in something like “if you call std::vector::reserve with an argument of N then the capacity of the vector is increased to at least N.”

    “That doesn’t tell you if the value has meaning”

    I don’t know what this “meaning” is that you’re talking about. Suppose someone passes you a string. You know nothing about it. What’s the meaning of the string? You can do anything you want with it. Ask for its size for example. This situation is the same as when the string has just been moved from.

    “unless you have a requirement that some operations are valid, this doesn’t help for an arbitrary type T”

    There is a requirement, and it is the “validity” requirement, where “valid” means “satisfies invariants”. From which you can deduce that all operations with no preconditions are valid in turn.

    Surely I misunderstand your use of the terms requirement and guarantee here… The way I understand it, all implementations of move operations are *required* to leave the moved-from objects in a state where all their functions that have no preconditions can be called. There is no guarantee in sight here.

    Regarding the unique_ptrs in your example, if it is possible for them get moved from at some point before the std::sort call, then you have to discover their state before using operator*! If you don’t, you are violating the preconditions of operator* by calling it on unique_ptrs that may have been moved from and may therefore be in any valid state, null being a valid state.

    “There cannot be, because the standard makes no explicit requirements on the type”

    I’m starting to think you underestimate “moved-from objects must satisfy invariants” as a perfectly reasonable requirement on move operations. I find no fault with it. Surely for optimization purposes more guarantees would help, but at what cost to implementors and users?

    “If you think you are left with an empty string, you are mistaken.”

    All I meant was that in practice you don’t get unusable garbage, not that all moved-from strings are empty. If your implementation of std::string swaps the contents then of course you may end up with your moved-from string being non-empty.

  38. > Actually, every function ever has a precondition that its arguments are in a valid state. That’s not saying much. What Herb is saying here is that moved-from objects are NOT in an invalid state.

    Different definitions of “valid” – if this were the case you could not destruct an invalid iterator. I was using valid in the iterator sense “has-meaning” as opposed to the way it is used by herb “satisfies invariants”.

    > The standard does state the precondtions of the functions it defines.

    That is a guarantee, not a requirement. Different things.

    > There is a way: you just use whatever tools you have at your disposal (usually member functions) that have no preconditions and allow you to determine what the state of the value is.

    That doesn’t tell you if the value has meaning – any type can provide stronger guarantees than what is required. But again unless you have a requirement that some operations are valid, this doesn’t help for an arbitrary type T. Also, inspecting a string doesn’t tell you if it has meaning – it may a value, but the value is meaningless. It is not like a NaN.

    > It is satisfied by the standard moveable types: unique_ptr, vector, unique_lock, etc.

    No, it is not satisfied. Let’s say I have:

    “`
    vector<unique_ptr> v;
    //…
    std::sort(begin(v), end(v), [](const auto& a, const auto& b){ return *a < *b; });
    “`
    Under the current phrasing, this code would be buggy. unique_ptr doesn’t satisfy the requirements of “must still meet the requirements of the library component that is using it”. You also cannot say that my comparison function is buggy because it _does_ satisfy the requirements of imposing a strict-weak ordering on the values in `v`.

    > Admittedly, it’s hard to come up with a obvious use case for the use-after-move scenario described above. I have never written this kind of code before. What matters is the spec though, and that compilers and library implementors follow it.

    There is not a use case for use-after-move of an arbitrary type. There cannot be, because the standard makes no explicit requirements on the type. It is super useful to know that a moved-from vector is empty – but a guarantee is different from a requirement. But even relying on that guarantee for vector requires comments and care because other containers, like string, do not make such a guarantee.

    > That is too weak a requirement for what’s actually happening in practice when you move from an object. “must be valid” as in “must be usable as you normally would” is more realistic. When you move from any standard movable type, in practice you’re not left with garbage: you get something very simple like an empty vector or an empty string or a non-owning unique_lock, all of which we have names for because they’re not taboo UB-boobytraps. You just have to go and discover for yourself what the state of the moved-from object is, but the standard guarantees it’s valid.

    If you think you are left with an empty string, you are mistaken. But having guarantees for particular types is useful, but you also need to know what the requirements are for using the standard library. Those are different things. The strong guarantee of “valid but unspecified” though breaks the efficient basis operations for the types. We end up with things like node-based containers that can’t guarantee a noexcept move construction.

    > Your guess is as good as mine as to why the committee did not want to specify those moved-from states in greater detail than with “valid but unspecified”.

    I don’t want the standard to change existing guarantees (well, I do, but that isn’t reasonable without a library versioning strategy). But I do want the standard to change the guarantees going forward so we can have a (more) efficient set of basis operations and to specify the requirements on for the standard library in a form that is actually actionable.

  39. @Sean

    “Your function `f` has a precondition that IndirectInt is fully-formed (i.e. “valid”).”

    Actually, every function ever has a precondition that its arguments are in a valid state. That’s not saying much. What Herb is saying here is that moved-from objects are NOT in an invalid state.

    “As a requirement, “unspecified” – is without meaning unless you state what operations MUST NOT have preconditions.”

    The standard does state the precondtions of the functions it defines. For example, it states plainly that calling .front() on an empty vector is UB. There is no need for any additional statements regarding the post conditions of moving from an object.

    “There is no way to inspect an unspecified value to determine if it has meaning or not. Continuing to perform operations with the string only generates a bigger mess.”

    There is a way: you just use whatever tools you have at your disposal (usually member functions) that have no preconditions and allow you to determine what the state of the value is. For example, after a std::vector has been moved from, you can check its .capacity(), .size(), .empty(), etc, and depending on the results of those calls, determine how you can or cannot use the vector. But at any rate you can keep using the vector however you want as if it had never been moved from. It’s just that you don’t know anything about it anymore.

    Example:

    #include <iostream>
    #include <vector>
    
    // No idea what that does to `v`. It may move it, for instance. Feel free to implement it.
    void mess_with_vector(std::vector<int>& v);
    
    int main()
    {
        std::vector<int> v { 1, 2, 3 };
    
        mess_with_vector(v);
        
        // From this point on, `v` is in a valid but unspecified state
    
        // std::vector::empty has no preconditions so I can call it
        if (v.empty())
        {
            std::cout << "The vector is empty!\n";
            // resize also has no preconditions
            v.resize(42);
            // operator[] requires that the argument is lower than the size, which is ok now
            v[0] = 666;
            std::cout << "Now the first element is " << v[0] << std::endl;
            // We are now using `v` as if nothing had happened above
        }
    }
    

    “The “must still meet the requirements of the library component that is using it” footnote is just weasel-words. In general, it is impossible to satisfy.”

    It is satisfied by the standard moveable types: unique_ptr, vector, unique_lock, etc.

    “there is not a single place in the standard library where it is necessary to do anything other than delete or assign to a moved-from object”

    Admittedly, it’s hard to come up with a obvious use case for the use-after-move scenario described above. I have never written this kind of code before. What matters is the spec though, and that compilers and library implementors follow it.

    “must be destructible and, if assignable, must be assignable-to”

    That is too weak a requirement for what’s actually happening in practice when you move from an object. “must be valid” as in “must be usable as you normally would” is more realistic. When you move from any standard movable type, in practice you’re not left with garbage: you get something very simple like an empty vector or an empty string or a non-owning unique_lock, all of which we have names for because they’re not taboo UB-boobytraps. You just have to go and discover for yourself what the state of the moved-from object is, but the standard guarantees it’s valid.

    Your guess is as good as mine as to why the committee did not want to specify those moved-from states in greater detail than with “valid but unspecified”.

    @Andrey

    If my understanding is correct, your example is typically what Herb would call a buggy class for the same reason IndirectInt was buggy. Moving from your File would leave it in a state where its invariant (that it must own an open file) is violated. Therefore the class is buggy unless, as you said, you offer a way to check for validity of the File and ask users to perform those checks.

    Either your class represents an open file or it’s movable but not both.

  40. * My phone typing skill are terrible. Instead of “Wishing this logic” it should be “With this logic”

  41. Following the logic that moved-from object should be in a valid state such that any operation without preconditions should be allowed to be called seems kinda off. Wishing this logic I don’t see a way how a move-only resource owner class that doesn’t not have a “null” state can be implemented in a simple to use way.

    For example, what if I want to implement class

    File

    that represents opened file. The simple class invariant (file is always opened) allows any IO operation on a class to be called at any time without prior check whether the file is opened or not (i.e. in a “null” state). Such a class is easy to implement and easy to use as callers does not need to check its state before using it. The only thing that user need to be careful about is not to use objects of this type after they are moved from. This is usually not hard to achieve as the move is either implicitly applied to a temporary object or it’s an explicit call to std::move. So user only need to make sure that moved from objects does not escape from places of explicit move.

    On the other hand, adding a ”null” state to the class invariant will add a lot of burden as it requires a check in every place when File object is about to be used just to make sure that the file object is not in “null” state.

  42. @Herb

    I’m not saying the iterators were moved from, I’m saying there is a precondition on iterators that they are valid before comparison. Your function `f` has a precondition that IndirectInt is fully-formed (i.e. “valid”). IndirectInt doesn’t contain a bug, nor does `f`. You simply have an unstated precondition.

    As a requirement, “unspecified” – is without meaning unless you state what operations MUST NOT have preconditions. That is very difficult, you can find counterexamples for nearly every operation in the standard. Destructing a thread can call terminate unless a set of preconditions are satisfied. Comparisons can be UB unless preconditions are satisfied.

    You use floating-point NaN as an example, but an unspecified value in std::string is a very different thing. There is no way to inspect an unspecified value to determine if it has meaning or not. Continuing to perform operations with the string only generates a bigger mess. Allowing a moved from string to escape into your system is a bug, by calling such a meaningless string “valid” (it is not) – you are preventing tools from catching such a mistake. NaNs and Infinities exist because checking the preconditions to ensure a math operation is valid _beforehand_ is prohibitively complex and time-consuming – NaNs allow you to check after-the-fact. If you know an operations postcondition is that it invalidates a value, there is no need for a NaN state. Iterators don’t have a NaN state.

    If I had a type that behaved like a NaN with regards to its moved-from state (you could call comparisons but they would all return false) it would not satisfy the requirements of comparison for sort, which requires that it impose a strict-weak order on all values in the sort. Try sorting a sequence with NaN values with std::sort…

    The “must still meet the requirements of the library component that is using it” footnote is just weasel-words. In general, it is impossible to satisfy. Luckily, there is not a single place in the standard library where it is necessary to do anything other than delete or assign to a moved-from object (or an object that may only satisfy the basic exception guarantee, which is also a partially-formed requirement). Get rid of the weasle-words and simply state the actual requirement of the operation:

    “An object which has been moved-from or was being modified when an exception was thrown must be destructible and, if assignable, must be assignable-to.”

    Done. You don’t have to talk about validity, unspecified, and partially vs. fully-formed, or meeting the requirements of the library component. That is the requirement.

  43. @Sean:
    > You would not say `f` has a bug if you replaced `IndirectInt` with `RandomAccessIterator`.

    But an iterator is a non-owner type, so move doesn’t apply. Move is for owner types, like unique_ptr or IndirectInt.

    Iterators (and ranges, views, etc.) are non-owners, and so can dangle. And when they do, it’s typically because they are invalidated by some other operation, not by an operation on the object itself, and the invalidated-ness is not because of their internal state but because the lifetime of a different object/buffer has ended. The only exception I can think of at the moment is input iterators, which do co-own an underlying state and so have always been an exception to general iterator guidance precisely because they are not just passive non-owning observers.

    Owners (e.g., unique_ptr, or the IndirectInt in this example) by construction cannot dangle, and should always be in a valid state or the type has a bug. And move is all about transferring ownership of owned resources.

    Perhaps you’re saying something else? If so could you illustrate it using a different example than iterators?

  44. Some opinions claims that we should be able to specify if moved-from objects can be used later – because of… destructors. unique_ptr is not a zero-overhead abstraction over raw pointers, because of destructors:

    But it could be zero-overhead with a new design. Just don’t allow use it after destruction, by marking its move constructor/operator with newly-invented attribute.

    The idea is similar to the explicit destructor call on automatic object that disallow further usage of the variable. I heard abut such paper but never actually saw it.

  45. If that’s the simple explanation, I don’t want to see the complex one.

  46. Some Rust fans seem to believe that using an object after move should be impossible, but the original purpose of move semantics was to recycle already reserved resources instead of getting new ones and freeing the old, not rendering the moved-from object unusable.

    It so happens that resource owning objects typically have some reasonable state without the resources, which it can go to on a succesful move, and continue to be usable without a performance hit: it has to go to a destructible state anyway.

  47. If we use an object after move it is a clear bug, irrespective of what standard says.
    In another way, if an object moves from one memory space to another, the object is no longer exists in its original space. Checking properties of an object which is no longer there doesn’t make any sense (Even if it is made to be valid to call the operation, the operation itself doesn’t carry any meaning).
    e.g. in `std::vector v {1,2,3,4}; auto v1 = std::move(v); v.empty();` whether standard allows the last statement or not, it’s result doesn’t carry any meaning. Simply because v is not guaranteed to be there, so there is no question whether v.empty() is true or false.
    I wish, c++ had a language backed use after move checker (like clang-tidy bugprone-use-after-move) as it is available in Rust.

  48. > What about objects that aren’t safe to be used normally after being moved from?

    This is where your argument goes off the rails.

    As a requirement, “valid but unspecified” has no meaning. As a guarantee, it is useless and damaging.

    If `std::sort` is comparing objects after they have been moved from, then it has bugs. Your `IndirectInt` is just fine, and no code should be passing an object in an unspecified state to some function `f`. You would not say `f` has a bug if you replaced `IndirectInt` with `RandomAccessIterator`. You would add the precondition that iterators be valid (in the proper sense used for iterators) and are within the same range.

    There is not a single library operation that requires anything more of a moved-from object other than destruction and assignment to it. These are precisely the requirements EoP calls “partially-formed.” *

    The standard should make the requirements clear regarding what operations must work for a moved-from type. The standard library would also be more efficient if the guarantees were relaxed on the standard type. i.e., we could have noexcept move-ctors for all the containers. Unfortunately, weakening guarantees for existing types is problematic, but at least new types do not have to pay the same penalty.

    * With the possible exception of self-swap. It isn’t clear if such aliasing is allowed by the standard but if it is, it adds the requirement that a moved-from object is move-assignable to itself. Move-assignment of an object in a state other than a moved-from state leads to a contradiction in postconditions.

  49. @Jo: including those as *explicit* assumptions makes the argument/point rather reasonable.

    But then I’d *still* say that move’s involvement is likely just a specific manifestation of a general problem, unless move is explicitly intended as the only way to get an object into that state.

  50. @bcs9 As the article says, “a moved-from object is in a state where “you can call its functions that have no preconditions,” or is in a “valid but unspecified state,” “.

    As you know, operator* on unique_ptr or operator[] on vector do have preconditions. However you can inspect the moved-from unique_ptr or vector to “rediscover” what state it’s in after the move by testing for nullity or for emptiness respectively.

    I think Herb’s example maybe needed to make explicit that IndirectInt has no affordances to check for validity, and that its operator< has no preconditions, making the class buggy. I hope I got this right.

  51. @HerbSutter, but what make’s IndirectInt’s “null state” different then std::unique_ptr’s or std::vector’s or an other std:: type that has preconditions for the validity of some of it’s operations?

    I’ll grant that have that condition explicitly be a “moved from” state (as opposed to a normal state that happens to be what move-from leaves) would be a bad idea. Are you asserting that having such a state that is *only* reachable by moving from is the issue? If so I’d heartily support that view but, at the same time, I’ve never personally seen anyone attempt that; is that something that is actually prevalent enough in the real world to be a concern?

  52. @bcs9 You can still compare smart pointers even if they’re null. A smart pointer where you can’t use operator* if it’s null is just a stateful type where if it’s in a given state it only supports a subset of its functions, but null is one of the valid states it can be in according to its invariant. But a special moved-from state that causes the object not to be valid and violate its intended invariant is buggy. (If you really intended that and document it, then see ‘but what about a third option’ Q&A.)

  53. How does

    IndirectInt::operator<

    differ from

    std::shared_ptr<T>::operator*

    ?

    In both cases, calling the function on a “null” values is invalid. However, by the argument above, the

    IndirectInt

    case has a bug while at the same time no claim seems to be made with regards to

    std::shared_ptr<T>

    , even thought the same argument seems to still apply.

  54. Thanks for this clarification on moved-on objects.

    This will make my day, tomorrow, at work… (We had a discussion on that very subject, no more than two weeks ago, and your post is my sweet, sweet comeback…).
    :-)

    Strangely, I’ve found that, sometimes, the cheapest way to move an object around, for example, when writing the move-assignment, was to… swap its content with the other instance. Of course, this is an implementation detail, and nothing in the interface suggest this behavior is guaranteed remain this way.

    But I find it ironic to see that std::swap is implemented in terms of moves, and that move-assignment can be cheaper in some cases with a swap of its members.
    :-)

  55. Thanks for this clarification on moved-on objects.

    This will make my day, tomorrow, at work… (We had a discussion on that very subject, no more than two weeks ago, and your post is my sweet, sweet comeback…).
    :-)

    Strangely, I’ve found that, sometimes, the cheapest way to move an object around, for example, when writing the move-assignment, was to… swap its content with the other instance. Of course, this is an implementation detail, and nothing in the interface suggest this behavior is guaranteed remain this way.

    But I find it ironic to see that std::swap is implemented in terms of moves, and that move-assignment can be cheaper in some cases with a swap of its members.
    :-)

Comments are closed.