Title
<future> still has type-erased allocators in promise
Status
lewg
Section
[futures.promise]
Submitter
Billy O'Neal III

Created on 2017-07-16.00:00:00 last changed yesterday

Messages

Date: 2024-11-19.16:09:33

Proposed resolution:

This wording is relative to N4988.

  1. Modify [futures.promise] as indicated:

    template <class R, class Alloc>
      struct uses_allocator<promise<R>, Alloc>;
    
    […]
    template <class R, class Alloc>
      struct uses_allocator<promise<R>, Alloc>
        : true_type { };
    

    -4- Preconditions: Alloc meets the Cpp17Allocator ([allocator.requirements.general]).

  2. Modify [futures.task.general] as indicated:

      template<class R, class... ArgTypes>
      class packaged_task<R(ArgTypes...)> {
      public:
        // construction and destruction
        packaged_task() noexcept;
        template<class F>
          explicit packaged_task(F&& f);
        template<class F, class Allocator>
          explicit packaged_task(allocator_arg_t, const Allocator& a, F&& f);
        ~packaged_task();
    
  3. Modify [futures.task.members] as indicated:

    template<class F>
      explicit packaged_task(F&& f);
    

    -?- Effects: Equivalent to packaged_task(allocator_arg, std::allocator<int>(), std::forward<F>(f)).

    [Drafting note: Uses of std::allocator<int> and std::allocator<unspecified> are not observable so this constructor can be implemented without delegating to the other constructor and without using `std::allocator`.]
    template<class F, class Allocator>
      packaged_task(allocator_arg_t, const Allocator& a, F&& f);
    

    -2- Constraints: remove_cvref_t<F> is not the same type as packaged_task<R(ArgTypes...)>.

    -3- Mandates: is_invocable_r_v<R, F&, ArgTypes...> is true.

    [Drafting note: Issue 4154 alters these Mandates: and Effects: but the two edits should combine cleanly.]

    -4- Preconditions: Invoking a copy of `f` behaves the same as invoking `f`. `Allocator` meets the Cpp17Allocator requirements ([allocator.requirements.general]).

    -5- Effects: Let `A2` be allocator_traits<Allocator>::rebind_alloc<unspecified> and let `a2` be an lvalue of type `A2` initialized with `A2(a)`. Creates a shared state and initializes the object's stored task with std::forward<F>(f). Uses `a2` to allocate storage for the shared state and stores a copy of `a2` in the shared state.

    -6- Throws: Any exceptions thrown by the copy or move constructor of `f`, or bad_alloc if memory for the internal data structures cannot be allocated. Any exceptions thrown by the initialization of the stored task. If storage for the shared state cannot be allocated, any exception thrown by `A2::allocate`.

    void reset();
    

    -26- Effects: As if Equivalent to:

    if (!valid())
      throw future_error(future_errc::no_state);
    *this = packaged_task(allocator_arg, a, std::move(f));
    
    where `f` is the task stored in `*this` and `a` is the allocator stored in the shared state.

    [Note 2: This constructs a new shared state for `*this`. The old state is abandoned ([futures.state]). — end note]

    -27- Throws:

    1. (27.1) — bad_alloc if memory for the new shared state cannot be allocated.
    2. (27.2) — Any exception thrown by the `packaged_task` constructor move constructor of the task stored in the shared state.
    3. (27.3) — `future_error` with an error condition of `no_state` if `*this` has no shared state.

Date: 2024-11-19.16:09:33

[ Wrocław 2024-11-18; LEWG would prefer a paper for this ]

Date: 2024-09-15.00:00:00

[ 2024-09-19; Jonathan provides improved wording ]

In July 2023 LEWG considered this and LWG issue 2095 and requested a new proposed resolution that kept the existing constructor (which is useful for controlling how the shared state is allocated) but removed the `uses_allocator` specialization that makes `promise` incorrectly claim to be allocator-aware. Some of the rationale in P2787R1 is applicable here too.

Without the `uses_allocator` specialization, there's no reason to provide an allocator-extended move constructor, resolving issue 2095.

And if we're going to continue supporting `std::promise` construction with an allocator, we could restore that for `std::packaged_task` too. That was removed by issue 2921, but issue 2976 argues that there was no good reason to do that. Removing `uses_allocator` for `packaged_task` would have made sense (as proposed below for `promise`) but 2921 didn't do that (which is why 2976 was needed). We can restore the `packaged_task` constructor that takes an allocator, and just not restore the `uses_allocator` specialization that implies it should be fully allocator-aware. Finally, if we restore that `packaged_task` constructor then we need to fix `reset()` as discussed in issue 2245.

In summary:

  • Keep support for using an allocator for shared state of a `promise`.
  • Remove `uses_allocator` specialization for `promise`.
  • Restore support for using an allocator for the shared state of `packaged_task`.
  • Do not restore `uses_allocator` specialization for `packaged_task`.
  • Update `packaged_task::reset()` to deal with allocators.

Date: 2024-09-20.12:40:58

[ Varna 2023-06-13; Change status to "LEWG" ]

This resolution is relative to N4659.

  1. Edit [futures.promise], class template promise synopsis, as indicated:

    template<class R>
    class promise {
    public:
      promise();
      template <class Allocator>
        promise(allocator_arg_t, const Allocator& a);
      […]
    };
    template <class R>
      void swap(promise<R>& x, promise<R>& y) noexcept;
    template <class R, class Alloc>
      struct uses_allocator<promise<R>, Alloc>;
    
    […]
    template <class R, class Alloc>
      struct uses_allocator<promise<R>, Alloc>
        : true_type { };
    

    -3- Requires: Alloc shall be an Allocator ([allocator.requirements]).

    promise();
    template <class Allocator>
      promise(allocator_arg_t, const Allocator& a);
    

    -4- Effects: constructs a promise object and a shared state. The second constructor uses the allocator a to allocate memory for the shared state.

Date: 2019-06-03.00:00:00

[ 2019-06-03 ]

Jonathan observes that this resolution conflicts with 2095.

Date: 2018-01-26.00:00:00

[ 2018-1-26 issues processing telecon ]

Status to 'Open'; Billy to write a paper.

Date: 2017-11-29.03:09:11

[ 28-Nov-2017 Mailing list discussion - set priority to P2 ]

Lots of people on the ML feel strongly about this; the suggestion was made that a paper would be welcomed laying out the rationale for removing allocator support here (and in other places).

Date: 2024-09-20.12:40:58

In Toronto Saturday afternoon LWG discussed LWG 2976 which finishes the job of removing allocator support from packaged_task. LWG confirmed that, despite the removal of packaged_task allocators "because it looks like std::function" was incorrect, they wanted to keep the allocator removals anyway, in large part due to this resolution being a response to an NB comment.

If we don't want the type erased allocator situation at all, then we should remove them from the remaining place they exist in <future>, namely, in promise.

This change also resolves potential implementation divergence on whether allocator::construct is intended to be used on elements constructed in the shared state, and allows the emplace-construction-in-future paper, p0319, to be implemented without potential problems there.

History
Date User Action Args
2024-11-19 16:09:33adminsetmessages: + msg14460
2024-09-20 12:40:58adminsetmessages: + msg14393
2024-09-20 12:26:44adminsetmessages: + msg14392
2024-09-20 12:26:44adminsetstatus: open -> lewg
2019-06-03 09:53:27adminsetmessages: + msg10413
2018-01-28 19:43:07adminsetmessages: + msg9656
2018-01-28 19:43:07adminsetstatus: new -> open
2017-11-29 03:09:11adminsetmessages: + msg9570
2017-07-16 14:02:00adminsetmessages: + msg9412
2017-07-16 00:00:00admincreate