This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++23 status.

3775. Broken dependencies in the Cpp17Allocator requirements

Section: 16.4.4.6.1 [allocator.requirements.general] Status: C++23 Submitter: Alisdair Meredith Opened: 2022-09-22 Last modified: 2023-11-22

Priority: Not Prioritized

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

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

View all issues with C++23 status.

Discussion:

This issue is extracted from P0177R2 as that paper stalled on the author's ability to update in time for C++17. While the issue was recorded and going to be resolved in the paper, we did not file an issue for the list when work on that paper stopped.

Many of the types and expressions in the Cpp17Allocator requirements are optional, and as such a default is provided that is exposed through std::allocator_traits. However, some types and operations are specified directly in terms of the allocator member, when really they should be specified allowing for reliance on the default, obtained through std::allocator_traits. For example, X::pointer is an optional type and not required to exist; XX::pointer is either X::pointer when it is present, or the default formula otherwise, and so is guaranteed to always exist, and the intended interface for user code. Observe that bullet list in p2, which acts as the key to the names in the Cpp17Allocator requirements, gets this right, unlike most of the text that follows.

This change corresponds to the known implementations, which meet the intended contract rather than that currently specified. For example, std::allocator does not provide any of the pointer related typedef members, so many of the default semantics indicated today would be ill-formed if implementations were not already implementing the fix.

An alternative resolution might be to add wording around p1-3 to state that if a name lookup fails then the default formula is used. However, it is simply clearer to write the constraints as intended, in the form of code that users can write, rather than hide behind a layer of indirect semantics that may be interpreted as requiring another layer of SFINAE metaprogramming.

[2022-10-12; Reflector poll]

Set status to Tentatively Ready after eight votes in favour during reflector poll.

[2022-11-12 Approved at November 2022 meeting in Kona. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4917.

  1. Modify 16.4.4.6.1 [allocator.requirements.general] as indicated:

    typename X::pointer
    

    -4- Remarks: Default: T*

    typename X::const_pointer
    

    -5- Mandates: XX::pointer is convertible to XX::const_pointer.

    -6- Remarks: Default: pointer_traits<XX::pointer>::rebind<const T>

    typename X::void_pointer
    typename Y::void_pointer
    

    -7- Mandates: XX::pointer is convertible to XX::void_pointer. XX::void_pointer and YY::void_pointer are the same type.

    -8- Remarks: Default: pointer_traits<XX::pointer>::rebind<void>

    typename X::const_void_pointer
    typename Y::const_void_pointer
    

    -9- Mandates: XX::pointer, XX::const_pointer, and XX::void_pointer are convertible to XX::const_void_pointer. XX::const_void_pointer and YY::const_void_pointer are the same type.

    -10- Remarks: Default: pointer_traits<XX::pointer>::rebind<const void>

    typename X::value_type
    

    -11- Result: Identical to T.

    typename X::size_type
    

    -12- Result: An unsigned integer type that can represent the size of the largest object in the allocation model.

    -13- Remarks: Default: make_unsigned_t<XX::difference_type>

    typename X::difference_type
    

    -14- Result: A signed integer type that can represent the difference between any two pointers in the allocation model.

    -15- Remarks: Default: pointer_traits<XX::pointer>::difference_type

    typename X::template rebind<U>::other
    

    -16- Result: Y

    -17- Postconditions: For all U (including T), YY::template rebindrebind_alloc<T>::other is X.

    -18- Remarks: If Allocator is a class template instantiation of the form SomeAllocator<T, Args>, where Args is zero or more type arguments, and Allocator does not supply a rebind member template, the standard allocator_traits template uses SomeAllocator<U, Args> in place of Allocator::rebind<U>::other by default. For allocator types that are not template instantiations of the above form, no default is provided.

    -19- [Note 1: The member class template rebind of X is effectively a typedef template. In general, if the name Allocator is bound to SomeAllocator<T>, then Allocator::rebind<U>::other is the same type as SomeAllocator<U>, where SomeAllocator<T>::value_type is T and SomeAllocator<U>::value_type is U. — end note]

    […]

    static_cast<XX::pointer>(w)
    

    -29- Result: XX::pointer

    -30- Postconditions: static_cast<XX::pointer>(w) == p.

    static_cast<XX::const_pointer>(x)
    

    -31- Result: XX::const_pointer

    -32- Postconditions: static_cast<XX::const_pointer>(x) == q.

    pointer_traits<XX::pointer>::pointer_to(r)
    

    -33- Result: XX::pointer

    -34- Postconditions: Same as p.

    a.allocate(n)
    

    -35- Result: XX::pointer

    […]

    a.allocate(n, y)
    

    -40- Result: XX::pointer

    […]

    a.allocate_at_least(n)
    

    -43- Result: allocation_result<XX::pointer>

    -44- Returns: allocation_result<XX::pointer>{ptr, count} where ptr is memory allocated for an array of count T and such an object is created but array elements are not constructed, such that count = n. If n == 0, the return value is unspecified.

    […]

    […]
    a.max_size()
    

    -50- Result: XX::size_type

    -51- Returns: The largest value n that can meaningfully be passed to X::a.allocate(n).

    -52- Remarks: Default: numeric_limits<size_type>::max() / sizeof(value_type)

    […]
    a == b
    

    -59- Result: bool

    -60- Returns: a == YY::rebind_alloc<T>::other(b).

    […]

    -92- An allocator type X shall meet the Cpp17CopyConstructible requirements (Table 33). The XX::pointer, XX::const_pointer, XX::void_pointer, and XX::const_void_pointer types shall meet the Cpp17NullablePointer requirements (Table 37). No constructor, comparison operator function, copy operation, move operation, or swap operation on these pointer types shall exit via an exception. XX::pointer and XX::const_pointer shall also meet the requirements for a Cpp17RandomAccessIterator (25.3.5.7) and the additional requirement that, when ap and (ap + n) are dereferenceable pointer values for some integral value n, addressof(*(ap + n)) == addressof(*ap) + n is true.

    -93- Let x1 and x2 denote objects of (possibly different) types XX::void_pointer, XX::const_void_pointer, XX::pointer, or XX::const_pointer. Then, x1 and x2 are equivalently-valued pointer values, if and only if both x1 and x2 can be explicitly converted to the two corresponding objects px1 and px2 of type XX::const_pointer, using a sequence of static_casts using only these four types, and the expression px1 == px2 evaluates to true.

    -94- Let w1 and w2 denote objects of type XX::void_pointer. Then for the expressions

    w1 == w2
    w1 != w2
    

    either or both objects may be replaced by an equivalently-valued object of type XX::const_void_pointer with no change in semantics.

    -95- Let p1 and p2 denote objects of type XX::pointer. Then for the expressions

    p1 == p2
    p1 != p2
    p1 < p2
    p1 <= p2
    p1 >= p2
    p1 > p2
    p1 - p2
    

    either or both objects may be replaced by an equivalently-valued object of type XX::const_pointer with no change in semantics.