Allocator destroy and fancy pointer operations must be non-throwing
Billy O'Neal III

Created on 2018-09-07.00:00:00, last changed 2018-10-01.03:18:34.


Date: 2018-10-01.03:18:34

Proposed resolution:

This wording is relative to N4762.

  1. Modify [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 ([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 [allocator.requirements], Table 33 "Cpp17Allocator requirements" as indicated:

    Table 33 — Cpp17Allocator requirements
    Expression Return type Assertion/note
    X::pointer Ssame as p.
    Throws: Nothing.
    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.
  3. Modify [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 ([random.access.iterators]) and of a contiguous iterator ([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 [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.

Date: 2018-10-01.03:18:34

[ 2018-09 Reflector prioritization ]

Set Priority to 3

Date: 2018-09-07.00:00:00

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 [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.

Date User Action Args
2018-10-01 03:18:34adminsetmessages: + msg10150
2018-09-08 12:00:08adminsetmessages: + msg10143
2018-09-07 00:00:00admincreate