3157. Allocator destroy and fancy pointer operations must be non-throwing

Section: 16.4.4.6 [allocator.requirements] Status: New Submitter: Billy O'Neal III Opened: 2018-09-07 Last modified: 2018-12-16

Priority: 3

View other active issues in [allocator.requirements].

View all other issues in [allocator.requirements].

View all issues with New status.

Discussion:

In annotating things required to be called by ~vector, Casey pointed out that several operations I guarded with noexcept aren't actually mandated by the standard to be noexcept. However, the STL, and more specifically here, containers, consider inability to destroy an element an unrecoverable condition. This is evidenced for the whole STL by 16.4.6.13 [res.on.exception.handling]/3 "Every destructor in the C ++ standard library shall behave as if it had a non-throwing exception specification.".

As a result, allocator::destroy and fancy pointer operations must be non-throwing for valid input, or the containers don't make any sense. This is obvious for things like vector::~vector, but less obviously the containers rely on these guarantees whenever inserting more than one element, etc.

Moreover, we too narrowly specify the domain of the pointer_traits::pointer_to requirement in the Cpp17Allocator requirements, because any node-based container that uses container-internal sentinel nodes needs to be able to form pointers to said sentinel nodes; that operation must also be non-throwing.

[2018-09 Reflector prioritization]

Set Priority to 3

Proposed resolution:

This wording is relative to N4762.

  1. Modify 16.4.4.6 [allocator.requirements], Table 32 "Descriptive variable definitions" as indicated:

    Table 32 — Descriptive variable definitions
    Variable Definition
    YY the type allocator_traits<Y>
    Z an allocator-aware container type (23.2.2 [container.requirements.general])
    y a value of type XX::const_void_pointer obtained by
    conversion from a result value of YY::allocate, or else a
    value of type (possibly const) std::nullptr_t.
    z an lvalue of type Z such that z.get_allocator() == a
    r1 a reference to any member subobject of z
    n a value of type XX::size_type.
  2. Modify 16.4.4.6 [allocator.requirements], Table 33 "Cpp17Allocator requirements" as indicated:

    Table 33 — Cpp17Allocator requirements
    Expression Return type Assertion/note
    pre-/post-condition
    Default
    pointer_traits<
    X::pointer
    >::pointer_to(r)
    X::pointer Ssame as p.
    Throws: Nothing.
    pointer_traits<
    X::pointer
    >::pointer_to(r1)
    A value of type YY::pointer or
    YY::const_pointer k such that
    *k is r1.
    Throws: Nothing.
    a.destroy(c) (not used) Effects: Destroys the object at c.
    Throws: Nothing.
    c->~C()
  3. Modify 16.4.4.6 [allocator.requirements], p5, as indicated:

    -5- An allocator type X shall satisfy the Cpp17CopyConstructible requirements (Table 26). The X::pointer, X::const_pointer, X::void_pointer, and X::const_void_pointer types shall satisfy the Cpp17NullablePointer requirements (Table 30). No constructor, comparison function, copy operation, move operation, or swap operation on these pointer types shall exit via an exception. X::pointer and X::const_pointer shall also satisfy the requirements for a random access iterator (24.3.5.7 [random.access.iterators]) and of a contiguous iterator (24.3.1 [iterator.requirements.general]) and operations in those requirements shall not exit via an exception so long as resulting iterators are dereferencable or past-the-end.

  4. Modify 20.2.9.3 [allocator.traits.members], as indicated:

    template<class T>
      static void destroy(Alloc& a, T* p);
    

    -6- Effects: Calls a.destroy(p) if that call is well-formed; otherwise, invokes p->~T().

    -?- Throws: Nothing.