2016. Allocators must be no-throw swappable

Section: 20.5.3.5 [allocator.requirements] Status: C++17 Submitter: Daniel Krügler Opened: 2010-11-17 Last modified: 2017-07-30

Priority: 2

View other active issues in [allocator.requirements].

View all other issues in [allocator.requirements].

View all issues with C++17 status.

Discussion:

During the Batavia meeting it turned out that there is a definition hole for types satisfying the Allocators requirements: The problem became obvious when it was discussed whether all swap functions of Containers with internal data handles can be safely tagged with noexcept or not. While it is correct that the implicit swap function of an allocator is required to be a no-throw operation (because move/copy-constructors and assignment operators are required to be no-throw functions), there are no such requirements for specialized swap overloads for a particular allocator.

But this requirement is essential because the Containers are required to support swappable Allocators, when the value allocator_traits<>::propagate_on_container_swap evaluates to true.

[2011-02-10 Alberto, Daniel, and Pablo collaborated on the proposed wording]

The proposed resolution (based on N3225) attempts to solve the following problems:

  1. Table 44 — Allocator requirements, expression rows X::propagate_on_container_copy_assignment, X::propagate_on_container_move_assignment, and X::propagate_on_container_swap only describe operations, but no requirements. In fact, if and only if these compile-time predicates evaluate to true, the additional requirements CopyAssignable, no-throw MoveAssignable, and no-throw lvalue Swappable, respectively, are imposed on the allocator types.
  2. 26.2.1 [container.requirements.general] p. 9 misses to refer to the correct swap conditions: The current wording does not relate to 20.5.3.2 [swappable.requirements] as it should and omits to mention that lvalues shall be swapped. Additional there is one situation described twice in p. 8 and p. 9 (undefined behaviour unless a.get_allocator() == b.get_allocator() or allocator_traits<allocator_type>::propagate_on_container_swap::value == true), which should be cleaned up.

[2011-04-08 Pablo comments]

I'm implementing a version of list now and I actually do find it impossible to write an exception-safe assignment operator unless I can assume that allocator assignment does not throw. (The problem is that I use a sentinel node and I need to allocate a new sentinel using the new allocator without destroying the old one -- then swap the allocator and sentinel pointer in atomically, without risk of an exception leaving one inconsistent with the other.

Please update the proposed resolution to add the nothrow requirement to copy-assignment.

[2014-02-14 Issaquah: Move to Ready]

Fix a couple of grammar issues related to calling swap and move to Ready.

Proposed resolution:

  1. Adapt the following three rows from Table 44 — Allocator requirements:

    Table 44 — Allocator requirements
    Expression Return type Assertion/note
    pre-/post-condition
    Default
    X::propagate_on_container_copy_assignment Identical to or derived from true_type
    or false_type
    true_type only if an allocator of type X should be copied
    when the client container is copy-assigned. See Note B, below.
    false_type
    X::propagate_on_container_move_assignment Identical to or derived from true_type
    or false_type
    true_type only if an allocator of type X should be moved
    when the client container is move-assigned. See Note B, below.
    false_type
    X::propagate_on_container_swap Identical to or derived from true_type
    or false_type
    true_type only if an allocator of type X should be swapped
    when the client container is swapped. See Note B, below.
    false_type
  2. Following 20.5.3.5 [allocator.requirements] p. 3 insert a new normative paragraph:

    Note B: If X::propagate_on_container_copy_assignment::value is true, X shall satisfy the CopyAssignable requirements (Table 39 [copyassignable]) and the copy operation shall not throw exceptions. If X::propagate_on_container_move_assignment::value is true, X shall satisfy the MoveAssignable requirements (Table 38 [moveassignable]) and the move operation shall not throw exceptions. If X::propagate_on_container_swap::value is true, lvalues of X shall be swappable (20.5.3.2 [swappable.requirements]) and the swap operation shall not throw exceptions.

  3. Modify 26.2.1 [container.requirements.general] p. 8 and p. 9 as indicated:

    8 - [..] The allocator may be replaced only via assignment or swap(). Allocator replacement is performed by copy assignment, move assignment, or swapping of the allocator only if allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value, allocator_traits<allocator_type>::propagate_on_container_move_assignment::value, or allocator_traits<allocator_type>::propagate_on_container_swap::value is true within the implementation of the corresponding container operation. The behavior of a call to a container's swap function is undefined unless the objects being swapped have allocators that compare equal or allocator_traits<allocator_type>::propagate_on_container_swap::value is true. In all container types defined in this Clause, the member get_allocator() returns a copy of the allocator used to construct the container or, if that allocator has been replaced, a copy of the most recent replacement.

    9 - The expression a.swap(b), for containers a and b of a standard container type other than array, shall exchange the values of a and b without invoking any move, copy, or swap operations on the individual container elements. Lvalues of aAny Compare, Pred, or Hash objectstypes belonging to a and b shall be swappable and shall be exchanged by unqualified calls to non-member calling swap as described in 20.5.3.2 [swappable.requirements]. If allocator_traits<allocator_type>::propagate_on_container_swap::value is true, then lvalues of allocator_type shall be swappable and the allocators of a and b shall also be exchanged using an unqualified call to non-memberby calling swap as described in 20.5.3.2 [swappable.requirements]. Otherwise, theythe allocators shall not be swapped, and the behavior is undefined unless a.get_allocator() == b.get_allocator(). Every iterator referring to an element in one container before the swap shall refer to the same element in the other container after the swap. It is unspecified whether an iterator with value a.end() before the swap will have value b.end() after the swap.