Title
Allocator requirements should not allow rebinding conversions to be explicit
Status
new
Section
[allocator.requirements.general]
Submitter
Jonathan Wakely

Created on 2024-08-02.00:00:00 last changed 3 months ago

Messages

Date: 2024-08-21.17:07:03

Proposed resolution:

This wording is relative to N4986.

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

Date: 2024-08-15.00:00:00

[ 2024-08-21; Reflector poll ]

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

Date: 2024-08-02.00:00:00

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.

History
Date User Action Args
2024-08-21 17:07:03adminsetmessages: + msg14332
2024-08-02 23:51:13adminsetmessages: + msg14300
2024-08-02 00:00:00admincreate