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

3917. Validity of allocator<void> and possibly polymorphic_allocator<void> should be clarified

Section: 20.2.10 [default.allocator], 20.4.3 [mem.poly.allocator.class] Status: New Submitter: Daniel Krügler Opened: 2023-04-08 Last modified: 2023-05-24

Priority: 3

View other active issues in [default.allocator].

View all other issues in [default.allocator].

View all issues with New status.

Discussion:

Before P0174 had been approved for the working paper, the validity of using void as template argument for std::allocator was obvious due to the existing specification of the explicit specialization allocator<void>.

This specialization was first moved to Annex D ([depr.default.allocator]) and later completely eradicated from the working paper via adoption of P0619.

The problem is that, since then, we have no explicit wording for std::allocator to support void as template argument any more. We fall now under the constraints for template "components" specified in 16.4.5.8 [res.on.functions] bullet 2.5.

-2- In particular, the behavior is undefined in the following cases:

  1. […]

  2. (2.5) — If an incomplete type (6.8.1 [basic.types.general]) is used as a template argument when instantiating a template component or evaluating a concept, unless specifically allowed for that component.

But no such allowance wording exists for allocator<void> nor for polymorphic_allocator<void>, more to the contrary, 16.4.4.6.1 [allocator.requirements.general] only refers to cv-unqualified object types as value types and void is not an object type.

Now we could argue that the wording is clear that instantiations of these are invalid, but there exists at least some indication that the instantiations are intended to be allowed.

As of 25.8.5 [coro.generator.promise] bullet (17.3) the static operator new members of std::generator mention a fall-back of using allocator<void>.

20.2.10.1 [default.allocator.general] says that all specializations of the default allocator meet the allocator completeness requirements (16.4.4.6.2 [allocator.requirements.completeness]), but albeit this specification does not specifically exclude the existence of an incomplete value type, the wording here does also not provide a definite statement, that it is valid (The wording originally was provided when we started adding support for (yet) incomplete value types that at some point later will become complete, but void can never be completed), since it is mostly focused on the completeness requirement for the allocator type itself.

The situation is similar (albeit maybe not that strong) for polymorphic_allocator<void>, since 20.4.3 [mem.poly.allocator.class] p1 has some unusual wording form that says

-1- A specialization of class template pmr::polymorphic_allocator meets the Cpp17Allocator requirements (16.4.4.6.1 [allocator.requirements.general]) if its template argument is a cv-unqualified object type.

and says then in p2:

-2- A specialization of class template pmr::polymorphic_allocator meets the allocator completeness requirements (16.4.4.6.2 [allocator.requirements.completeness]) if its template argument is a cv-unqualified object type.

Again, this wording is not conclusive, whether void is intended to be supported, it is certainly not completely ruled out, but that is not strong enough to counterpart 16.4.5.8 [res.on.functions] (2.5). It is maybe worth pointing out that for a while we were considering to use void as default template argument for pmr::polymorphic_allocator, see e.g. P0339R0, but that thought was later replaced by deciding for std::byte instead, which is a complete object type.

I assume that at least the intention exists that std::allocator is intended to support incomplete types, maybe also for polymorphic_allocator. If polymorphic_allocator is intended to support incomplete types as well, we should also amend 20.4.3.3 [mem.poly.allocator.mem] p1 and p8 with a Mandates: element similarly as we did for std::allocator via LWG 3307(i).

[2023-05-24; Reflector poll]

Set priority to 3 after reflector poll.

Proposed resolution:

This wording is relative to N4944.

  1. Add the following new paragraph at the end of 20.2.10.1 [default.allocator.general] as indicated:

    -2- allocator_traits<allocator<T>>::is_always_equal::value is true for any T.

    -?- The template parameter T of allocator may be an incomplete type.

  2. Add the following new paragraph at the end of 20.4.3.1 [mem.poly.allocator.class.general] (possibly just after the class template synopsis) as indicated:

    -?- The template parameter Tp of polymorphic_allocator may be an incomplete type.

  3. Modify 20.4.3.3 [mem.poly.allocator.mem] as indicated:

    [Drafting note: The reference to sizeof(Tp) gives indirect evidence that we want to exclude incomplete types here, but we cannot rely on the "equivalent to" magic formula, because that is defined conditionally]

    [[nodiscard]] Tp* allocate(size_t n);
    

    -?- Mandates: Tp is not an incomplete type (6.8.1 [basic.types.general]).

    -1- Effects: If numeric_limits<size_t>::max() / sizeof(Tp) < n, throws bad_array_new_length. Otherwise equivalent to:

    return static_cast<Tp*>(memory_rsrc->allocate(n * sizeof(Tp), alignof(Tp)));
    

    [Drafting note: We don't need extra wording for the member templates allocate_object, deallocate_object, new_object, delete_object, or construct, because their semantics does not depend on template parameter Tp and the general wording of 16.4.5.8 [res.on.functions] (2.5) again requires the completeness of T here.

    For deallocate we also omit the completeness requirement (as we did so for allocator::deallocate), because this is indirectly implied by the existing precondition. ]