This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of LEWG status.
<future>
still has type-erased allocators in promise
Section: 32.10.6 [futures.promise] Status: LEWG Submitter: Billy O'Neal III Opened: 2017-07-16 Last modified: 2024-10-02
Priority: 2
View other active issues in [futures.promise].
View all other issues in [futures.promise].
View all issues with LEWG 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.
<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.
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:
promise
.uses_allocator
specialization for promise
.packaged_task
.uses_allocator
specialization for packaged_task
.packaged_task::reset()
to deal with allocators.Proposed resolution:
This wording is relative to N4988.
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]).
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); template<class F, class Allocator> explicit packaged_task(allocator_arg_t, const Allocator& a, F&& f); ~packaged_task();
Modify 32.10.10.2 [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 ofstd::allocator<int>
andstd::allocator<unspecified>
are not observable so this constructor can be implemented without delegating to the other constructor and without usingstd::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 aspackaged_task<R(ArgTypes...)>
.-3- Mandates:
is_invocable_r_v<R, F&, ArgTypes...>
istrue
.[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 invokingf
.Allocator
meets the Cpp17Allocator requirements (16.4.4.6.1 [allocator.requirements.general]).-5- Effects: Let
A2
beallocator_traits<Allocator>::rebind_alloc<unspecified>
and leta2
be an lvalue of typeA2
initialized withA2(a)
. Creates a shared state and initializes the object's stored task withstd::forward<F>(f)
. Usesa2
to allocate storage for the shared state and stores a copy ofa2
in the shared state.-6- Throws:
Any exceptions thrown by the copy or move constructor ofAny exceptions thrown by the initialization of the stored task. If storage for the shared state cannot be allocated, any exception thrown byf
, or bad_alloc if memory for the internal data structures cannot be allocated.A2::allocate
.…
void reset();-26- Effects:
As ifEquivalent to:if (!valid()) throw future_error(future_errc::no_state); *this = packaged_task(allocator_arg, a, std::move(f));wheref
is the task stored in*this
anda
is the allocator stored in the shared state.[Note 2: This constructs a new shared state for
*this
. The old state is abandoned (32.10.5 [futures.state]). — end note]-27- Throws:
(27.1) — bad_alloc if memory for the new shared state cannot be allocated.- (27.2) — Any exception thrown by the
packaged_task
constructormove constructor of the task stored in the shared state.- (27.3) —
future_error
with an error condition ofno_state
if*this
has no shared state.