2384. Allocator's deallocate function needs better specification

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

Priority: 3

View other active issues in [allocator.requirements].

View all other issues in [allocator.requirements].

View all issues with C++17 status.

Discussion:

According to Table 28, 20.5.3.5 [allocator.requirements], an Allocator's deallocate function is specified as follows:

All n T objects in the area pointed to by p shall be destroyed prior to this call. n shall match the value passed to allocate to obtain this memory. Does not throw exceptions. [Note: p shall not be singular. — end note]

This wording is confusing in regard to the following points:

  1. This specification does not make clear that the result of an allocate call can only be returned once to the deallocate function. This is much clearer expressed for operator delete (21.6.2.1 [new.delete.single] p12, emphasis mine):

    Requires: ptr shall be a null pointer or its value shall be a value returned by an earlier call to the (possibly replaced) operator new(std::size_t) or operator new(std::size_t,const std::nothrow_t&) which has not been invalidated by an intervening call to operator delete(void*).

  2. The intended meaning of that wording was to say that deallocate shall accept every result value that had been returned by a corresponding call to allocate, this includes also a possible result of a null pointer value, which is possible ("[Note: If n == 0, the return value is unspecified. — end note]"). Unfortunately the deallocate function uses a non-normative note ("p shall not be singular.") which refers to the fuzzy term singular, that is one of the most unclear and misunderstood terms of the library, as pointed out in 1213. The occurrence of this term has lead to the possible understanding, that this function would never allow null pointer values. Albeit for allocators the intention had not been to require the support in general that a null pointer value can be provided to deallocate (as it is allowed for std::free and operator delete), the mental model was that every returned value of allocate shall be an acceptable argument type of the corresponding deallocate function.

This issue is not intending to enforce a specific meaning of singular iterator values, but the assertion is that this note does more harm than good. In addition to wording from operator delete there is no longer any need to obfuscate the normative wording.

[2014-05-24 Alisdair comments]

Now that I am reading it very precisely, there is another mis-stated assumption as a precondition for deallocate:

All n T objects in the area pointed to by p shall be destroyed prior to this call.

This makes a poor assumption that every possible object in the allocated buffer was indeed constructed, but this is often not the case, e.g., a vector that is not filled to capacity. We should require calling the destructor for only those objects actually constructed in the buffer, which may be fewer than n, or even 0.

I wonder if we really require all objects to be destroyed before calling deallocate though. Are we really so concerned about leaking objects that might not manage resources? Should this not be the proper concern of the library managing the objects and memory?

[2014-06-05 Daniel responds and improves wording]

I fully agree with the last comment and I think that this requirement should be removed. We have no such requirements for comparable functions such as operator delete or return_temporary_buffer(), and this wording seems to be a wording rudiment that exists since C++98.

[2015-05, Lenexa]

Marshall: What do people think about this?
PJP: Sure.
Wakely: Love it.
Marshall: Ready?
Everyone agrees.

Proposed resolution:

This wording is relative to N3936.

  1. Change Table 28 ("Allocator requirements") as indicated:

    Table 28 — Allocator requirements
    Expression Return type Assertion/note
    pre-/post-condition
    Default
    a.deallocate(p,n) (not used) Pre: p shall be a value returned by an earlier
    call to allocate which has not been invalidated by
    an intervening call to deallocate. n shall
    match the value passed to allocate to obtain this
    memory.
    All n T objects in the area pointed to by
    p shall be destroyed prior to this call.

    Throws: Nothing.n
    shall match the value passed to
    allocate to obtain this
    memory. Does not throw
    exceptions. [Note: p shall not
    be singular. — end note]