Skip to content

Instantly share code, notes, and snippets.

@sean-parent
Last active March 7, 2020 12:38
Show Gist options
  • Save sean-parent/fed31bee69bc41d888f84f25743da9f1 to your computer and use it in GitHub Desktop.
Save sean-parent/fed31bee69bc41d888f84f25743da9f1 to your computer and use it in GitHub Desktop.

The use of "valid but unspecified" as a guarantee is useless and damaging. Consider std::list<>. A guarantee of std::list<> is that on move the iterators are not invalidated (different use of valid), except for the end iterator. The end iterator frequently points directly to an empty terminal node directly in the local instance so moving a list invalidates the end iterator. Invalidation can be a source of bugs so a library implementor decided their implementation would strengthen the guarantee by ensuring that the end iterator was valid even with a move. To do so, they heap-allocate the terminal node for each list.

If you move-construct this list, the rhs argument must be "valid but unspecified". To achieve that, an end node must be allocated in the rhs. Now the move constructor is not noexcept. Not having a noexcept move constructor is problematic because it makes it impossible to get robust transactional behavior to restore a system after a throw. Trying to complete a transaction by moving in the result could throw, not just losing our transaction but our complete state. This is why std::variant, instead of simply requiring noexcept move, ended up with a "could be empty because exception" state.

If the requirement on move is only that the rhs argument is partially-formed, then we no longer have to guarantee the list is empty, and so it no longer requires a terminal node.

The counter-argument is, "but then I can't even call empty() on a moved-from list! And I have no way to check!". But imagine the list represents all the list of your employees. What good can come from attempting to read the list when the value is now unspecified? It is no longer a valid list of your employees (this is the same notion of valid used for iterators, "has meaning"), even if we've managed to maintain all the invariants of the class. To make it valid again, we have to restore meaning - by assigning a new value to the list, or we can destroy it. You want the compiler to be able to say, "Hey! Those are not your employees - don't look at that!".

Assign-to and destruct are the only two required operations on a moved-from instance for the standard library to work, even if unstated. Any given type is free to provide stronger guarantees, std::vector can continue to define its moved-from state as empty.

IMO the required semantics and pre- and postconditions of the operations on well-formed and partially-formed regular-types, should be baked into the language. The compiler is currently free to elide calls to the copy constructor because it is free to assume the semantics of the copy constructor. Requiring proper semantics would open up more opportunities for the compiler and sanitizers to catch mistakes as well as for the compiler to optimize accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment