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.

4128. Allocator requirements should not allow rebinding conversions to be explicit

Section: 16.4.4.6.1 [allocator.requirements.general] Status: New Submitter: Jonathan Wakely Opened: 2024-08-02 Last modified: 2024-08-21

Priority: 3

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

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

View all issues with New status.

Discussion:

The Cpp17Allocator requirements require a non-explicit copy constructor, but do not require a non-explicit converting constructor used for rebinding.

Since C++14 it has been clearly stated that "An allocator type X shall satisfy the requirements of CopyConstructible". That requires X u = a; to work as well as X u(a);, but only when the type of a is X. Constructing a rebound allocator from another specialization of the same allocator class template is only required to work using direct-initialization, X u(b);. This means it's permitted to make the converting constructor explicit, so that X u = b; is ill-formed. There seems to be no good reason to allow allocators to make that ill-formed.

In fact, there seems to be a good reason to not allow it. The uses_allocator trait is defined in terms of is_convertible, not is_constructible, which means that if Alloc<T> has an explicit converting constructor, then uses_allocator_v<X, Alloc<T>> will be false for a type with X::allocator_type = Alloc<U>, because you would need to explicitly convert an Alloc<T> to Alloc<U> before passing it to X's constructor.

That is at least consistent: the trait gives the right answer even for allocators with explicit conversions. It doesn't seem very useful though, and if users don't carefully check uses_allocator with exactly the right types they might get errors unless they carefully rebind and convert their allocators explicitly. Or worse, if they rely on other library components to check uses_allocator, they might silently get the wrong behaviour. For example, trying to construct an X with an Alloc<T> (e.g. via make_obj_using_allocator) could silently fail to pass the allocator to the constructor because there's no implicit conversion to X::allocator_type, even though you're providing an allocator of the right "family" and trying to use it. So a constructor without an allocator argument could be chosen when you thought you were supplying an allocator for the object to use.

There seemed to be consensus when LWG discussed it that we should just require conversions to be implicit, so that allocators are not silently ignored because they cannot be implicitly converted.

During the discussion it was noted that assigning allocators is only needed when the propagation trait is true, but such assignments are always done between objects of the same allocator type. So the allocator requirements do not need to require converting assignments to work.

[2024-08-21; Reflector poll]

Set priority to 3 after reflector poll. Jonathan to add an Annex C entry about the change.

Proposed resolution:

This wording is relative to N4986.

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

    X u(a);
    X u = a;
    

    -63- Postconditions: u == a is true.

    -64- Throws: Nothing.

    X u(b);
    X u = b;
    

    -65- Postconditions: Y(u) == b and u == X(b) are both true.

    -66- Throws: Nothing.

    X u(std::move(a));
    X u = std::move(a);
    

    -67- Postconditions: The value of a is unchanged and is equal to u.

    -68- Throws: Nothing.

    X u(std::move(b));
    X u = std::move(b);
    

    -69- Postconditions: u is equal to the prior value of X(b).

    -70- Throws: Nothing.