985. Allowing throwing move

Section: 26.2.1 [container.requirements.general] Status: Resolved Submitter: Rani Sharoni Opened: 2009-02-12 Last modified: 2016-02-10

Priority: Not Prioritized

View other active issues in [container.requirements.general].

View all other issues in [container.requirements.general].

View all issues with Resolved status.

Discussion:

Introduction

This proposal is meant to resolve potential regression of the N2800 draft, see next section, and to relax the requirements for containers of types with throwing move constructors.

The basic problem is that some containers operations, like push_back, have a strong exception safety guarantee (i.e. no side effects upon exception) that are not achievable when throwing move constructors are used since there is no way to guarantee revert after partial move. For such operations the implementation can at most provide the basic guarantee (i.e. valid but unpredictable) as it does with multi copying operations (e.g. range insert).

For example, vector<T>::push_back() (where T has a move constructor) might resize the vector and move the objects to the new underlying buffer. If move constructor throws it might not be possible to recover the throwing object or to move the old objects back to the original buffer.

The current draft is explicit by disallowing throwing move for some operations (e.g. vector<>::reserve) and not clear about other operations mentioned in 26.2.1 [container.requirements.general]/10 (e.g. single element insert): it guarantees strong exception safety without explicitly disallowing a throwing move constructor.

Regression

This section only refers to cases in which the contained object is by itself a standard container.

Move constructors of standard containers are allowed to throw and therefore existing operations are broken, compared with C++03, due to move optimization. (In fact existing implementations like Dinkumware are actually throwing).

For example, vector< list<int> >::reserve yields undefined behavior since list<int>'s move constructor is allowed to throw. On the other hand, the same operation has strong exception safety guarantee in C++03.

There are few options to solve this regression:

  1. Disallow throwing move and throwing default constructor
  2. Disallow throwing move but disallowing usage after move
  3. Special casing
  4. Disallow throwing move and making it optional

Option 1 is suggested by proposal N2815 but it might not be applicable for existing implementations for which containers default constructors are throwing.

Option 2 limits the usage significantly and it's error prone by allowing zombie objects that are nothing but destructible (e.g. no clear() is allowed after move). It also potentially complicates the implementation by introducing special state.

Option 3 is possible, for example, using default construction and swap instead of move for standard containers case. The implementation is also free to provide special hidden operation for non throwing move without forcing the user the cope with the limitation of option-2 when using the public move.

Option 4 impact the efficiency in all use cases due to rare throwing move.

The proposed wording will imply option 1 or 3 though option 2 is also achievable using more wording. I personally oppose to option 2 that has impact on usability.

Relaxation for user types

Disallowing throwing move constructors in general seems very restrictive since, for example, common implementation of move will be default construction + swap so move will throw if the default constructor will throw. This is currently the case with the Dinkumware implementation of node based containers (e.g. std::list) though this section doesn't refer to standard types.

For throwing move constructors it seem that the implementation should have no problems to provide the basic guarantee instead of the strong one. It's better to allow throwing move constructors with basic guarantee than to disallow it silently (compile and run), via undefined behavior.

There might still be cases in which the relaxation will break existing generic code that assumes the strong guarantee but it's broken either way given a throwing move constructor since this is not a preserving optimization.

[ Batavia (2009-05): ]

Bjarne comments (referring to his draft paper): "I believe that my suggestion simply solves that. Thus, we don't need a throwing move."

Move to Open and recommend it be deferred until after the next Committee Draft is issued.

[ 2009-10 Santa Cruz: ]

Should wait to get direction from Dave/Rani (N2983).

[ 2010-03-28 Daniel updated wording to sync with N3092. ]

The suggested change of 26.3.8.4 [deque.modifiers]/2 should be removed, because the current wording does say more general things:

2 Remarks: If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of T there are no effects. If an exception is thrown by the move constructor of a non-CopyConstructible T, the effects are unspecified.

The suggested change of 26.3.11.3 [vector.capacity]/2 should be removed, because the current wording does say more general things:

2 Effects: A directive that informs a vector of a planned change in size, so that it can manage the storage allocation accordingly. After reserve(), capacity() is greater or equal to the argument of reserve if reallocation happens; and equal to the previous value of capacity() otherwise. Reallocation happens at this point if and only if the current capacity is less than the argument of reserve(). If an exception is thrown other than by the move constructor of a non-CopyConstructible type, there are no effects.

[2011-03-15: Daniel updates wording to sync with N3242 and comments]

The issue has nearly been resolved by previous changes to the working paper, in particular all suggested changes for deque and vector are no longer necessary. The still remaining parts involve the unordered associative containers.

[2011-03-24 Madrid meeting]

It looks like this issue has been resolved already by noexcept paper N3050

Rationale:

Resolved by N3050

Proposed resolution:

26.2.1 [container.requirements.general] paragraph 10 add footnote:

-10- Unless otherwise specified (see 26.2.6.1 [associative.reqmts.except], 26.2.7.1 [unord.req.except], 26.3.8.4 [deque.modifiers], and 26.3.11.5 [vector.modifiers]) all container types defined in this Clause meet the following additional requirements:

[Note: for compatibility with C++ 2003, when "no effect" is required, standard containers should not use the value_type's throwing move constructor when the contained object is by itself a standard container. — end note]

26.2.7.1 [unord.req.except] change paragraph 2+4 to say:

-2- For unordered associative containers, if an exception is thrown by any operation other than the container's hash function from within an insert() function inserting a single element, the insert() function has no effect unless the exception is thrown by the contained object move constructor.

[…]

-4- For unordered associative containers, if an exception is thrown from within a rehash() function other than by the container's hash function or comparison function, the rehash() function has no effect unless the exception is thrown by the contained object move constructor.

Keep 26.3.8.4 [deque.modifiers] paragraph 2 unchanged [Drafting note: The originally proposed wording did suggest to add a last sentence as follows:

If an exception is thrown by push_back() or emplace_back() function, that function has no effects unless the exception is thrown by the move constructor of T.

end drafting note ]

-2- Remarks: If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of T there are no effects. If an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.

Keep 26.3.11.3 [vector.capacity] paragraph 2 unchanged [Drafting note: The originally proposed wording did suggest to change the last sentence as follows:

If an exception is thrown, there are no effects unless the exception is thrown by the contained object move constructor.

end drafting note ]

-2- Effects: A directive that informs a vector of a planned change in size, so that it can manage the storage allocation accordingly. After reserve(), capacity() is greater or equal to the argument of reserve if reallocation happens; and equal to the previous value of capacity() otherwise. Reallocation happens at this point if and only if the current capacity is less than the argument of reserve(). If an exception is thrown other than by the move constructor of a non-CopyInsertable type, there are no effects.

Keep 26.3.11.3 [vector.capacity] paragraph 12 unchanged [Drafting note: The originally proposed wording did suggest to change the old paragraph as follows:

-12- Requires: If value_type has a move constructor, that constructor shall not throw any exceptions. If an exception is thrown, there are no effects unless the exception is thrown by the contained object move constructor.

end drafting note ]

-12- Requires: If an exception is thrown other than by the move constructor of a non-CopyInsertable T there are no effects.

Keep 26.3.11.5 [vector.modifiers] paragraph 1 unchanged [Drafting note: The originally proposed wording did suggest to change the old paragraph as follows:

-1- Requires: If value_type has a move constructor, that constructor shall not throw any exceptions. Remarks: If an exception is thrown by push_back() or emplace_back() function, that function has no effect unless the exception is thrown by the move constructor of T.

end drafting note ]

-1- Remarks: Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid. If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of T or by any InputIterator operation there are no effects. If an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.