Skip to content

Commit b44cd81

Browse files
Implement P3503R3 Make Type-Erased Allocator Use In promise And packaged_task Consistent (#5630)
Co-authored-by: Stephan T. Lavavej <[email protected]>
1 parent 1c2c4d4 commit b44cd81

File tree

12 files changed

+335
-55
lines changed

12 files changed

+335
-55
lines changed

stl/inc/functional

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,7 @@ constexpr bool _Testable_callable_v =
719719
template <class _Ty>
720720
bool _Test_callable(const _Ty& _Arg) noexcept { // determine whether std::function must store _Arg
721721
if constexpr (_Testable_callable_v<_Ty>) {
722-
return !!_Arg;
722+
return static_cast<bool>(_Arg);
723723
} else {
724724
return true;
725725
}
@@ -755,7 +755,6 @@ template <class _Impl> // determine whether _Impl must be dynamically allocated
755755
constexpr bool _Is_large = sizeof(_Impl) > _Space_size || alignof(_Impl) > alignof(max_align_t)
756756
|| !_Impl::_Nothrow_move::value;
757757

758-
#if _HAS_FUNCTION_ALLOCATOR_SUPPORT
759758
template <class _Callable, class _Alloc, class _Rx, class... _Types>
760759
class _Func_impl final : public _Func_base<_Rx, _Types...> {
761760
// derived class for specific implementation types that use allocators
@@ -834,7 +833,6 @@ private:
834833

835834
_Compressed_pair<_Alloc, _Callable> _Mypair;
836835
};
837-
#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT
838836

839837
template <class _Ty, class... _Types>
840838
_Ty* _Global_new(_Types&&... _Args) { // acts as "new" while disallowing user overload selection
@@ -989,7 +987,6 @@ protected:
989987
}
990988
}
991989

992-
#if _HAS_FUNCTION_ALLOCATOR_SUPPORT
993990
template <class _Fx, class _Alloc>
994991
void _Reset_alloc(_Fx&& _Val, const _Alloc& _Ax) { // store copy of _Val with allocator
995992
if (!_STD _Test_callable(_Val)) { // null member pointer/function pointer/std::function
@@ -1012,7 +1009,6 @@ protected:
10121009
_Set(_Ptr);
10131010
}
10141011
}
1015-
#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT
10161012

10171013
void _Tidy() noexcept {
10181014
if (!_Empty()) { // destroy callable object and maybe delete it
@@ -1141,6 +1137,7 @@ public:
11411137
"The target function object type must be copy constructible (N4140 [func.wrap.func.con]/7).");
11421138
this->_Reset_alloc(_STD forward<_Fx>(_Func), _Ax);
11431139
}
1140+
#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT
11441141

11451142
template <class _SecretTag, class _Fx, class _Alloc,
11461143
enable_if_t<is_same_v<_SecretTag, _Secret_copyability_ignoring_tag>, int> = 0,
@@ -1149,7 +1146,6 @@ public:
11491146
// used exclusively for packaged_task
11501147
this->_Reset_alloc(_STD forward<_Fx>(_Func), _Ax);
11511148
}
1152-
#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT
11531149

11541150
function& operator=(const function& _Right) {
11551151
function(_Right).swap(*this);
@@ -1160,11 +1156,16 @@ public:
11601156
this->_Reset_move(_STD move(_Right));
11611157
}
11621158

1163-
#if _HAS_FUNCTION_ALLOCATOR_SUPPORT
1164-
template <class _Alloc>
1165-
function(allocator_arg_t, const _Alloc& _Al, function&& _Right) {
1159+
template <class _SecretTag, class _Alloc,
1160+
enable_if_t<is_same_v<_SecretTag, _Secret_copyability_ignoring_tag>, int> = 0>
1161+
explicit function(_SecretTag, allocator_arg_t, const _Alloc& _Al, function&& _Right) {
11661162
this->_Reset_alloc(_STD move(_Right), _Al);
11671163
}
1164+
1165+
#if _HAS_FUNCTION_ALLOCATOR_SUPPORT
1166+
template <class _Alloc>
1167+
function(allocator_arg_t, const _Alloc& _Al, function&& _Right)
1168+
: function(_Secret_copyability_ignoring_tag{}, allocator_arg, _Al, _STD move(_Right)) {}
11681169
#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT
11691170

11701171
function& operator=(function&& _Right) noexcept /* strengthened */ {

stl/inc/future

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,25 @@ struct _State_deleter : _Deleter_base<_Ty> { // manage allocator and deletion st
187187
_Alloc _My_alloc;
188188
};
189189

190+
template <class _Ty>
191+
struct __declspec(novtable) _Clonable_deleter_base : _Deleter_base<_Ty> {
192+
// TRANSITION, ABI, should be fused into _Deleter_base
193+
virtual _Associated_state<_Ty>* _Move_clone(_Associated_state<_Ty>&&) = 0;
194+
};
195+
196+
template <class _Ty, class _Derived, class _Alloc>
197+
struct _State_deleter_v2 : _Clonable_deleter_base<_Ty> { // manage allocator and deletion state objects
198+
_State_deleter_v2(const _Alloc& _Al) : _My_alloc(_Al) {}
199+
200+
_State_deleter_v2(const _State_deleter_v2&) = delete;
201+
_State_deleter_v2& operator=(const _State_deleter_v2&) = delete;
202+
203+
void _Delete(_Associated_state<_Ty>* _State) noexcept override;
204+
_Associated_state<_Ty>* _Move_clone(_Associated_state<_Ty>&& _Src) override;
205+
206+
_Alloc _My_alloc;
207+
};
208+
190209
template <class _Ty>
191210
union _Result_holder {
192211
_Result_holder() noexcept {}
@@ -389,6 +408,10 @@ public:
389408
}
390409
}
391410

411+
_Mydel* _Get_deleter() const noexcept { // get deleter for cloning
412+
return _Deleter;
413+
}
414+
392415
protected:
393416
void _Maybe_run_deferred_function(unique_lock<mutex>& _Lock) { // run a deferred function if not already done
394417
if (!_Running) { // run the function
@@ -495,19 +518,13 @@ public:
495518
template <class _Fty2, enable_if_t<!is_same_v<_Remove_cvref_t<_Fty2>, _Function_type>, int> = 0>
496519
explicit _Packaged_state(_Fty2&& _Fnarg) : _Fn(_Secret_copyability_ignoring_tag{}, _STD forward<_Fty2>(_Fnarg)) {}
497520

498-
#if _HAS_FUNCTION_ALLOCATOR_SUPPORT
499-
template <class _Alloc>
500-
_Packaged_state(const _Function_type& _Fnarg, const _Alloc& _Al, _Mydel* _Dp)
501-
: _Mybase(_Dp), _Fn(allocator_arg, _Al, _Fnarg) {}
502-
503521
template <class _Alloc>
504522
_Packaged_state(_Function_type&& _Fnarg, const _Alloc& _Al, _Mydel* _Dp)
505-
: _Mybase(_Dp), _Fn(allocator_arg, _Al, _STD move(_Fnarg)) {}
523+
: _Mybase(_Dp), _Fn(_Secret_copyability_ignoring_tag{}, allocator_arg, _Al, _STD move(_Fnarg)) {}
506524

507525
template <class _Fty2, class _Alloc, enable_if_t<!is_same_v<_Remove_cvref_t<_Fty2>, _Function_type>, int> = 0>
508526
_Packaged_state(_Fty2&& _Fnarg, const _Alloc& _Al, _Mydel* _Dp)
509527
: _Mybase(_Dp), _Fn(_Secret_copyability_ignoring_tag{}, allocator_arg, _Al, _STD forward<_Fty2>(_Fnarg)) {}
510-
#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT
511528

512529
void _Call_deferred(_ArgTypes... _Args) { // set deferred call
513530
_TRY_BEGIN
@@ -543,10 +560,7 @@ public:
543560
_CATCH_END
544561
}
545562

546-
const auto& _Get_fn() const& {
547-
return _Fn;
548-
}
549-
auto&& _Get_fn() && noexcept {
563+
_Function_type&& _Get_fn() && noexcept {
550564
return _STD move(_Fn);
551565
}
552566

@@ -569,11 +583,14 @@ _Associated_state<_Ty>* _Make_associated_state(const _Alloc& _Al) {
569583
return _STD _Unfancy(_Res.release()); // ownership transferred to caller
570584
}
571585

572-
#if _HAS_FUNCTION_ALLOCATOR_SUPPORT
573586
template <class _Pack_state, class _Fty2, class _Alloc>
574587
_Pack_state* _Make_packaged_state(_Fty2&& _Fnarg, const _Alloc& _Al) {
575588
// construct a _Packaged_state object with an allocator from an rvalue function object
576-
using _Delty = _State_deleter<typename _Pack_state::_Mybase::_State_type, _Pack_state, _Alloc>;
589+
#ifdef _CPPRTTI // TRANSITION, ABI, should not rely on RTTI
590+
using _Delty = _State_deleter_v2<typename _Pack_state::_Mybase::_State_type, _Pack_state, _Alloc>;
591+
#else // ^^^ defined(_CPPRTTI) / !defined(_CPPRTTI) vvv
592+
using _Delty = _State_deleter<typename _Pack_state::_Mybase::_State_type, _Pack_state, _Alloc>;
593+
#endif // ^^^ !defined(_CPPRTTI) ^^^
577594
using _Aldelty = _Rebind_alloc_t<_Alloc, _Delty>;
578595
using _Alstate = _Rebind_alloc_t<_Alloc, _Pack_state>;
579596

@@ -584,7 +601,26 @@ _Pack_state* _Make_packaged_state(_Fty2&& _Fnarg, const _Alloc& _Al) {
584601
(void) _Del.release(); // ownership of _Del.get() now transferred to _Res
585602
return _STD _Unfancy(_Res.release()); // ownership transferred to caller
586603
}
587-
#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT
604+
605+
template <class _Ty, class _Derived, class _Alloc>
606+
void _State_deleter_v2<_Ty, _Derived, _Alloc>::_Delete(_Associated_state<_Ty>* _State) noexcept {
607+
// delete _State and this using stored allocator
608+
using _State_allocator = _Rebind_alloc_t<_Alloc, _Derived>;
609+
_State_allocator _St_alloc(_My_alloc);
610+
611+
using _Deleter_allocator = _Rebind_alloc_t<_Alloc, _State_deleter_v2>;
612+
_Deleter_allocator _Del_alloc(_My_alloc);
613+
614+
_Derived* _Ptr = static_cast<_Derived*>(_State);
615+
616+
_STD _Delete_plain_internal(_St_alloc, _Ptr);
617+
_STD _Delete_plain_internal(_Del_alloc, this);
618+
}
619+
620+
template <class _Ty, class _Derived, class _Alloc>
621+
_Associated_state<_Ty>* _State_deleter_v2<_Ty, _Derived, _Alloc>::_Move_clone(_Associated_state<_Ty>&& _Src) {
622+
return _STD _Make_packaged_state<_Derived>(static_cast<_Derived&&>(_Src)._Get_fn(), _My_alloc);
623+
}
588624

589625
template <class _Rx>
590626
class _Deferred_async_state : public _Packaged_state<_Rx()> {
@@ -1185,9 +1221,6 @@ private:
11851221
_Promise<int> _MyPromise;
11861222
};
11871223

1188-
template <class _Ty, class _Alloc>
1189-
struct uses_allocator<promise<_Ty>, _Alloc> : true_type {};
1190-
11911224
_EXPORT_STD template <class _Ty>
11921225
void swap(promise<_Ty>& _Left, promise<_Ty>& _Right) noexcept {
11931226
_Left.swap(_Right);
@@ -1211,21 +1244,19 @@ public:
12111244
template <class _Fty2, enable_if_t<!is_same_v<_Remove_cvref_t<_Fty2>, packaged_task>, int> = 0>
12121245
explicit packaged_task(_Fty2&& _Fnarg) : _MyPromise(new _MyStateType(_STD forward<_Fty2>(_Fnarg))) {
12131246
static_assert(_Is_invocable_r<_Ret, decay_t<_Fty2>&, _ArgTypes...>::value,
1214-
"The function object must be callable with _ArgTypes... and return _Ret (N4988 [futures.task.members]/3).");
1247+
"The function object must be callable with _ArgTypes... and return _Ret (N5014 [futures.task.members]/4).");
12151248
}
12161249

12171250
packaged_task(packaged_task&&) noexcept = default;
12181251

12191252
packaged_task& operator=(packaged_task&&) noexcept = default;
12201253

1221-
#if _HAS_FUNCTION_ALLOCATOR_SUPPORT
12221254
template <class _Fty2, class _Alloc, enable_if_t<!is_same_v<_Remove_cvref_t<_Fty2>, packaged_task>, int> = 0>
1223-
packaged_task(allocator_arg_t, const _Alloc& _Al, _Fty2&& _Fnarg)
1255+
explicit packaged_task(allocator_arg_t, const _Alloc& _Al, _Fty2&& _Fnarg)
12241256
: _MyPromise(_STD _Make_packaged_state<_MyStateType>(_STD forward<_Fty2>(_Fnarg), _Al)) {
12251257
static_assert(_Is_invocable_r<_Ret, decay_t<_Fty2>&, _ArgTypes...>::value,
1226-
"The function object must be callable with _ArgTypes... and return _Ret (N4140 [futures.task.members]/2).");
1258+
"The function object must be callable with _ArgTypes... and return _Ret (N5014 [futures.task.members]/4).");
12271259
}
1228-
#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT
12291260

12301261
~packaged_task() noexcept {
12311262
_MyPromise._Get_state()._Abandon();
@@ -1270,7 +1301,20 @@ public:
12701301
void reset() { // reset to newly constructed state
12711302
_MyStateManagerType& _State_mgr = _MyPromise._Get_state_for_set();
12721303
_MyStateType& _MyState = *static_cast<_MyStateType*>(_State_mgr._Ptr());
1273-
_MyPromiseType _New_promise(new _MyStateType(_STD move(_MyState)._Get_fn()));
1304+
1305+
const auto _New_state_ptr = [&_MyState]() -> _Associated_state<_P_arg_type_t<_Ret>>* {
1306+
#ifdef _CPPRTTI // TRANSITION, ABI, should not rely on RTTI
1307+
using _Clonable_deleter_t = _Clonable_deleter_base<_P_arg_type_t<_Ret>>;
1308+
if (const auto _Clonable_deleter = dynamic_cast<_Clonable_deleter_t*>(_MyState._Get_deleter())) {
1309+
return _Clonable_deleter->_Move_clone(_STD move(_MyState));
1310+
} else {
1311+
return new _MyStateType(_STD move(_MyState)._Get_fn());
1312+
}
1313+
#else // ^^^ defined(_CPPRTTI) / !defined(_CPPRTTI) vvv
1314+
return new _MyStateType(_STD move(_MyState)._Get_fn());
1315+
#endif // ^^^ !defined(_CPPRTTI) ^^^
1316+
}();
1317+
_MyPromiseType _New_promise{_New_state_ptr};
12741318
_MyPromise._Get_state()._Abandon();
12751319
_MyPromise._Swap(_New_promise);
12761320
}
@@ -1294,11 +1338,6 @@ template <class _Fx>
12941338
packaged_task(_Fx) -> packaged_task<typename _Deduce_signature<_Fx>::type>;
12951339
#endif // _HAS_CXX17
12961340

1297-
#if _HAS_FUNCTION_ALLOCATOR_SUPPORT
1298-
template <class _Ty, class _Alloc>
1299-
struct uses_allocator<packaged_task<_Ty>, _Alloc> : true_type {};
1300-
#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT
1301-
13021341
_EXPORT_STD template <class _Ty>
13031342
void swap(packaged_task<_Ty>& _Left, packaged_task<_Ty>& _Right) noexcept {
13041343
_Left.swap(_Right);

stl/inc/yvals_core.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
// P3223R2 Making istream::ignore() Less Surprising
8484
// P3323R1 Forbid atomic<cv T>, Specify atomic_ref<cv T>
8585
// (for atomic<cv T>)
86+
// P3503R3 Make Type-Erased Allocator Use In promise And packaged_task Consistent
8687

8788
// _HAS_CXX17 directly controls:
8889
// P0005R4 not_fn()
@@ -153,8 +154,6 @@
153154
// P0298R3 std::byte
154155
// P0302R1 Removing Allocator Support In std::function
155156
// LWG-2385 function::assign allocator argument doesn't make sense
156-
// LWG-2921 packaged_task and type-erased allocators
157-
// LWG-2976 Dangling uses_allocator specialization for packaged_task
158157
// Enforcement of matching allocator value_types
159158

160159
// _HAS_CXX17 and _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS control:
@@ -1032,8 +1031,6 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect
10321031

10331032
// P0302R1 Removing Allocator Support In std::function
10341033
// LWG-2385 function::assign allocator argument doesn't make sense
1035-
// LWG-2921 packaged_task and type-erased allocators
1036-
// LWG-2976 Dangling uses_allocator specialization for packaged_task
10371034
#ifndef _HAS_FUNCTION_ALLOCATOR_SUPPORT
10381035
#define _HAS_FUNCTION_ALLOCATOR_SUPPORT (!_HAS_CXX17)
10391036
#endif // !defined(_HAS_FUNCTION_ALLOCATOR_SUPPORT)

tests/libcxx/expected_results.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ std/language.support/support.limits/support.limits.general/cstring.version.compi
145145
# libc++ has not implemented P3323R1: "Forbid atomic<cv T>, Specify atomic_ref<cv T>"
146146
std/atomics/atomics.ref/member_types.compile.pass.cpp FAIL
147147

148+
# libc++ has not implemented P3503R3 "Make Type-Erased Allocator Use In promise And packaged_task Consistent"
149+
std/thread/futures/futures.promise/uses_allocator.pass.cpp FAIL
150+
std/thread/futures/futures.task/futures.task.members/ctor2.compile.pass.cpp FAIL
151+
148152
# Various bogosity (LLVM-D141004)
149153
std/utilities/utility/mem.res/mem.res.pool/mem.res.pool.ctor/ctor_does_not_allocate.pass.cpp FAIL
150154
std/utilities/utility/mem.res/mem.res.pool/mem.res.pool.ctor/sync_with_default_resource.pass.cpp FAIL

tests/std/test.lst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,7 @@ tests\P2693R1_text_formatting_header_thread
721721
tests\P2693R1_text_formatting_stacktrace
722722
tests\P2693R1_text_formatting_thread_id
723723
tests\P3107R5_enabled_specializations
724+
tests\P3503R3_packaged_task_promise_with_allocator
724725
tests\VSO_0000000_allocator_propagation
725726
tests\VSO_0000000_any_calling_conventions
726727
tests\VSO_0000000_c_math_functions

tests/std/tests/Dev10_561430_list_and_tree_leaks/test.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ int main() {
157157
f.get();
158158
}
159159

160-
#if _HAS_FUNCTION_ALLOCATOR_SUPPORT
161160
{
162161
packaged_task<int()> pt(allocator_arg, Mallocator<int>(), [] { return 1234; });
163162

@@ -200,7 +199,6 @@ int main() {
200199

201200
f.get();
202201
}
203-
#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT
204202
#endif // _M_CEE_PURE
205203

206204
assert(g_mallocs == 0);

tests/std/tests/Dev11_0920385_list_sort_allocator/test.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,6 @@ int main() {
254254
f.get();
255255
}
256256

257-
#if _HAS_FUNCTION_ALLOCATOR_SUPPORT
258257
{
259258
packaged_task<int()> pt(allocator_arg, alloc, [] { return 1234; });
260259
future<int> f = pt.get_future();
@@ -276,7 +275,6 @@ int main() {
276275
pt();
277276
f.get();
278277
}
279-
#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT
280278
#endif // _M_CEE_PURE
281279

282280
test_DevDiv_1119194();

tests/std/tests/GH_000140_adl_proof_construction/test.compile.pass.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,11 @@ void test_packaged_task() {
130130
packaged_task<void(int)>{validating_identity{}};
131131
packaged_task<void(int)>{validating_large_identity{}};
132132

133-
#if !_HAS_CXX17
134133
packaged_task<void(validator)>{allocator_arg, adl_proof_allocator<unsigned char>{}, simple_identity{}};
135134
packaged_task<void(validator)>{allocator_arg, adl_proof_allocator<unsigned char>{}, simple_large_identity{}};
136135

137136
packaged_task<void(int)>{allocator_arg, adl_proof_allocator<unsigned char>{}, validating_identity{}};
138137
packaged_task<void(int)>{allocator_arg, adl_proof_allocator<unsigned char>{}, validating_large_identity{}};
139-
#endif // !_HAS_CXX17
140138
}
141139

142140
void test_promise() {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
RUNALL_INCLUDE ..\impure_matrix.lst

0 commit comments

Comments
 (0)