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

3003. <future> still has type-erased allocators in promise

Section: 32.10.6 [futures.promise] Status: Resolved Submitter: Billy O'Neal III Opened: 2017-07-16 Last modified: 2025-06-23

Priority: 2

View all other issues in [futures.promise].

View all issues with Resolved status.

Discussion:

In Toronto Saturday afternoon LWG discussed LWG 2976(i) 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.

[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).

[2018-1-26 issues processing telecon]

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

[2019-06-03]

Jonathan observes that this resolution conflicts with 2095(i).

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

Previous resolution [SUPERSEDED]:

This resolution is relative to N4659.

  1. Edit 32.10.6 [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 (16.4.4.6 [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.

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

In July 2023 LEWG considered this and LWG issue 2095(i) 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(i).

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(i), but issue 2976(i) 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(i).

In summary:

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

[2025-06-21 Status changed: LEWG → Resolved.]

Resolved by adoption of P3503R3 in Sofia.

Proposed resolution:

This wording is relative to N4988.

  1. Modify 32.10.6 [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 (16.4.4.6.1 [allocator.requirements.general]).

  2. Modify 32.10.10.1 [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);
    
        ~packaged_task();
    
  3. Modify 32.10.10.2 [futures.task.members] as indicated:

    template<class F>
      explicit packaged_task(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.]
    
    

    -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(i) 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.

    -5- Effects: Creates a shared state and initializes the object's stored task with std::forward<F>(f).

    -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.

    void reset();
    

    -26- Effects: As if

    
    *this = packaged_task( std::move(f));
    
    where f is the task stored in *this .

    [Note 2: This constructs a new shared state for *this. The old state is abandoned (32.10.5 [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 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.