Document number: N3264=11-0034
Date: 2011-03-25
Project: Programming Language C++, Library Working Group
Reply-to: Beman Dawes <bdawes at acm dot org>

CH-18 and US-85: Clarifying the state of moved-from objects (Revision 1)

Introduction

FCD comments CH-18 and US-85 request the state of moved-from objects be clarified. They are closely related but not identical. This proposal provides proposed wording in one place to ensure consistent resolution of both.

National Body comments

CH-18 = LWG-1353. [FCD] Clarify the state of a moved-from object

The general approach on moving is that a library object after moving out is in a "valid but unspecified state". But this is stated at the single object specifications, which is error prone (especially if the move operations are implicit) and unnecessary duplication.

[ Resolution proposed by ballot comment ]

Consider putting a general statement to the same effect into clause 17.

US-85 = LWG-1374. [FCD] Clarify moved-from objects are "toxic"

20.2.1 Table 34 "MoveConstructible requirements" says "Note: rv remains a valid object. Its state is unspecified". Some components give stronger guarantees. For example, moved-from shared_ptrs are guaranteed empty (20.9.11.2.1/25). In general, what the standard really should say (preferably as a global blanket statement) is that moved-from objects can be destroyed and can be the destination of an assignment. Anything else is radioactive. For example, containers can be "emptier than empty". This needs to be explicit and required generally.

Note: The last time that one of us mentioned "emptier than empty" (i.e. containers missing sentinel nodes, etc.) the objection was that containers can store sentinel nodes inside themselves in order to avoid dynamically allocating them. This is unacceptable because

(a) it forces existing implementations (i.e. Dinkumware's, Microsoft's, IBM's, etc.) to change for no good reason (i.e. permitting more operations on moved-from objects), and

(b) it invalidates end-iterators when swapping containers. (The Working Paper currently permits end-iterator invalidation, which we consider to be wrong, but that's a separate argument. In any event, mandating end-iterator invalidation is very different from permitting it.)

[ Resolution proposed in ballot comment ]

State as a general requirement that moved-from objects can be destroyed and can be the destination of an assignment. Any other use is undefined behavior.

Rationale

Howard Hinnant provided the following rationale. The proposed resolution was crafted accordingly.

There are two versions of what is required of moved-from objects.  One version for std::defined types.  One version for user-defined types.

The version for std::defined types is stricter and represents what I believe is "best practice".  I.e. it is effectively a coding guideline which leads by example.  It implicitly tells people the best way to code with respect to move semantics.  And we should require just what Beman has written with std::vector as an example.

The version for user-defined types is weaker.  It is the weakest requirements you can reasonably get without breaking std algorithms.  std::algorithms generally can't perform anything but a limited set of operations on a user-defined type.  These operations are listed in the requirements for each individual algorithm or class.  We should require that these operations always work, whether involving a moved-from state or not.  We should not require that the user get anything else working in a moved-from state.  Except for the container adaptors, we have no algorithms that will call empty() or front() on user-defined types (and in only that case do user-defined types have to have reasonable behavior for those members).

Proposed Resolution

Changes clarifying state of moved-from objects for standard library defined types

Add a new definition to 17.3 Definitions [definitions]:

17.3.24 [defns.valid.unspecified]
valid but unspecified state
an object state that is not specified except that the object's invariants are met and operations on the object behave as specified.

[Example: If an object x of type std::vector<int> is in a valid but unspecified state, x.empty() can be called unconditionally, and x.front() can be called provided x.empty() returns false. --end example]

Add a new sub-section, 17.6.4.15 Moved-from state of library types [movedfrom.lib.types]:

Objects of types defined in the standard library may be moved from ([class.copy]). 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 ([defns.valid.unspecified]).

Strike the indicated text from 20.8.14.2.1 function construct/copy/destroy [func.wrap.func.con], ¶13:

Effects: Replaces the target of *this with the target of f , leaving f in a valid but unspecified state.

Strike the indicated text from 21.4.2 basic_string constructors and assigment operators [string.cons], ¶21:

Effects: If *this and str are not the same object, modifies *this as shown in Table 71. The constructor leaves str in a valid but unspecified state. [ Note: A valid implementation is swap(str). —end note ]

21.4.2 basic_string constructors and assigment operators [string.cons] fails to mention the moved-from state for several other move constructors and move assignments. It is thus an example of why moved-from state should be specified in the clause 17 front matter rather than on a function by function basis.

Strike the indicated text from 21.4.6.3 basic_string::assign [string::assign], ¶2:

Effects: The function replaces the string controlled by *this with a string of length str.size() whose elements are a copy of the string controlled by str. Leaves str in a valid but unspecified state. [ Note: A valid implementation is swap(str). —end note ]

Strike the indicated text from 26.6.2.1 valarray constructors [valarray.cons], ¶6:

The array created by this constructor has the same length as the argument array. The elements are initialized with the values of the corresponding elements of the argument array. After construction, v is in a valid but unspecified state.

Strike the indicated text from 26.6.2.2 valarray assignment [valarray.assign], ¶3:

Effects: *this obtains the value of v. After the assignment, v is in a valid but unspecified state. If the length of v is not equal to the length of *this, resizes *this to make the two arrays the same length, as if by calling resize(v.size()), before performing the assignment.

Strike the indicated text from 28.10.1 match_results constructors [re.results.const], ¶5:

Effects: Move-constructs an object of class match_results from m satisfying the same postconditions as Table 141. Additionally, the stored Allocator value is move constructed from m.get_allocator(). After the initialization of *this, sets m to an unspecified but valid state.

Strike the indicated text from 28.10.1 match_results constructors [re.results.const], ¶8:

Effects: Move-assigns m to *this. The postconditions of this function are indicated in Table 141. After the assignment, m is in a valid but unspecified state.

Changes clarifying state of moved-from objects for user-defined types

Change Table 34 - MoveConstructible requirements [moveconstructible] as indicated:

[ Note: rv remains a valid object. Its 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 ]

Change Table 36 - MoveAssignable requirements [moveassignable] as indicated:

[ Note: rv remains a valid object. Its 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 ]

Acknowledgements

Alisdair Meredith, Daniel Krügler, and Howard Hinnant were instrumental in preparing this paper. Many thanks!