From 58d59fde98c65bb37c5a637879d1f83975f122ed Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Tue, 1 Jul 2025 07:25:21 +0800 Subject: [PATCH 1/9] Implement P3503R3 --- stl/inc/functional | 19 +- stl/inc/future | 95 +++++-- stl/inc/yvals_core.h | 5 +- tests/libcxx/expected_results.txt | 4 + tests/std/test.lst | 1 + .../Dev10_561430_list_and_tree_leaks/test.cpp | 2 - .../test.cpp | 2 - .../test.compile.pass.cpp | 2 - .../env.lst | 4 + .../test.cpp | 250 ++++++++++++++++++ .../test.compile.pass.cpp | 4 - tests/tr1/tests/future/test.cpp | 6 +- 12 files changed, 339 insertions(+), 55 deletions(-) create mode 100644 tests/std/tests/P3503R3_packaged_task_promise_with_allocator/env.lst create mode 100644 tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp diff --git a/stl/inc/functional b/stl/inc/functional index 6496b8d3dff..6991fce0390 100644 --- a/stl/inc/functional +++ b/stl/inc/functional @@ -713,7 +713,7 @@ constexpr bool _Testable_callable_v = template bool _Test_callable(const _Ty& _Arg) noexcept { // determine whether std::function must store _Arg if constexpr (_Testable_callable_v<_Ty>) { - return !!_Arg; + return static_cast(_Arg); } else { return true; } @@ -749,7 +749,6 @@ template // determine whether _Impl must be dynamically allocated constexpr bool _Is_large = sizeof(_Impl) > _Space_size || alignof(_Impl) > alignof(max_align_t) || !_Impl::_Nothrow_move::value; -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT template class _Func_impl final : public _Func_base<_Rx, _Types...> { // derived class for specific implementation types that use allocators @@ -828,7 +827,6 @@ private: _Compressed_pair<_Alloc, _Callable> _Mypair; }; -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT template _Ty* _Global_new(_Types&&... _Args) { // acts as "new" while disallowing user overload selection @@ -983,7 +981,6 @@ protected: } } -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT template void _Reset_alloc(_Fx&& _Val, const _Alloc& _Ax) { // store copy of _Val with allocator if (!_STD _Test_callable(_Val)) { // null member pointer/function pointer/std::function @@ -1006,7 +1003,6 @@ protected: _Set(_Ptr); } } -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT void _Tidy() noexcept { if (!_Empty()) { // destroy callable object and maybe delete it @@ -1135,6 +1131,7 @@ public: "The target function object type must be copy constructible (N4140 [func.wrap.func.con]/7)."); this->_Reset_alloc(_STD forward<_Fx>(_Func), _Ax); } +#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT template , int> = 0, @@ -1143,7 +1140,6 @@ public: // used exclusively for packaged_task this->_Reset_alloc(_STD forward<_Fx>(_Func), _Ax); } -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT function& operator=(const function& _Right) { function(_Right).swap(*this); @@ -1154,11 +1150,16 @@ public: this->_Reset_move(_STD move(_Right)); } -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT - template - function(allocator_arg_t, const _Alloc& _Al, function&& _Right) { + template , int> = 0> + explicit function(_SecretTag, allocator_arg_t, const _Alloc& _Al, function&& _Right) { this->_Reset_alloc(_STD move(_Right), _Al); } + +#if _HAS_FUNCTION_ALLOCATOR_SUPPORT + template + function(allocator_arg_t, const _Alloc& _Al, function&& _Right) + : function(_Secret_copyability_ignoring_tag{}, allocator_arg, _Al, _STD move(_Right)) {} #endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT function& operator=(function&& _Right) noexcept /* strengthened */ { diff --git a/stl/inc/future b/stl/inc/future index 318427ba744..8c0a8385ad0 100644 --- a/stl/inc/future +++ b/stl/inc/future @@ -187,6 +187,25 @@ struct _State_deleter : _Deleter_base<_Ty> { // manage allocator and deletion st _Alloc _My_alloc; }; +template +struct __declspec(novtable) _Clonable_deleter_base : _Deleter_base<_Ty> { + // TRANSITION, ABI, should be fused into _Deleter_base + virtual _Associated_state<_Ty>* _Move_clone(_Associated_state<_Ty>&&) = 0; +}; + +template +struct _State_deleter_v2 : _Clonable_deleter_base<_Ty> { // manage allocator and deletion state objects + _State_deleter_v2(const _Alloc& _Al) : _My_alloc(_Al) {} + + _State_deleter_v2(const _State_deleter_v2&) = delete; + _State_deleter_v2& operator=(const _State_deleter_v2&) = delete; + + void _Delete(_Associated_state<_Ty>* _State) noexcept override; + _Associated_state<_Ty>* _Move_clone(_Associated_state<_Ty>&& _Src) override; + + _Alloc _My_alloc; +}; + template union _Result_holder { _Result_holder() noexcept {} @@ -389,6 +408,10 @@ public: } } + _Mydel* _Get_deleter() const noexcept { // get deleter for cloning + return _Deleter; + } + protected: void _Maybe_run_deferred_function(unique_lock& _Lock) { // run a deferred function if not already done if (!_Running) { // run the function @@ -495,19 +518,13 @@ public: template , _Function_type>, int> = 0> explicit _Packaged_state(_Fty2&& _Fnarg) : _Fn(_Secret_copyability_ignoring_tag{}, _STD forward<_Fty2>(_Fnarg)) {} -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT - template - _Packaged_state(const _Function_type& _Fnarg, const _Alloc& _Al, _Mydel* _Dp) - : _Mybase(_Dp), _Fn(allocator_arg, _Al, _Fnarg) {} - template _Packaged_state(_Function_type&& _Fnarg, const _Alloc& _Al, _Mydel* _Dp) - : _Mybase(_Dp), _Fn(allocator_arg, _Al, _STD move(_Fnarg)) {} + : _Mybase(_Dp), _Fn(_Secret_copyability_ignoring_tag{}, allocator_arg, _Al, _STD move(_Fnarg)) {} template , _Function_type>, int> = 0> _Packaged_state(_Fty2&& _Fnarg, const _Alloc& _Al, _Mydel* _Dp) : _Mybase(_Dp), _Fn(_Secret_copyability_ignoring_tag{}, allocator_arg, _Al, _STD forward<_Fty2>(_Fnarg)) {} -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT void _Call_deferred(_ArgTypes... _Args) { // set deferred call _TRY_BEGIN @@ -543,10 +560,7 @@ public: _CATCH_END } - const auto& _Get_fn() const& { - return _Fn; - } - auto&& _Get_fn() && noexcept { + _Function_type&& _Get_fn() && noexcept { return _STD move(_Fn); } @@ -569,11 +583,14 @@ _Associated_state<_Ty>* _Make_associated_state(const _Alloc& _Al) { return _STD _Unfancy(_Res.release()); // ownership transferred to caller } -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT template _Pack_state* _Make_packaged_state(_Fty2&& _Fnarg, const _Alloc& _Al) { // construct a _Packaged_state object with an allocator from an rvalue function object - using _Delty = _State_deleter; +#ifdef _CPPRTTI // TRANSITON, ABI, should not rely on RTTI + using _Delty = _State_deleter_v2; +#else // ^^^ defined(_CPPRTTI) / !defined(_CPPRTTI) vvv + using _Delty = _State_deleter; +#endif // ^^^ !defined(_CPPRTTI) ^^^ using _Aldelty = _Rebind_alloc_t<_Alloc, _Delty>; using _Alstate = _Rebind_alloc_t<_Alloc, _Pack_state>; @@ -584,7 +601,26 @@ _Pack_state* _Make_packaged_state(_Fty2&& _Fnarg, const _Alloc& _Al) { (void) _Del.release(); // ownership of _Del.get() now transferred to _Res return _STD _Unfancy(_Res.release()); // ownership transferred to caller } -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT + +template +void _State_deleter_v2<_Ty, _Derived, _Alloc>::_Delete(_Associated_state<_Ty>* _State) noexcept { + // delete _State and this using stored allocator + using _State_allocator = _Rebind_alloc_t<_Alloc, _Derived>; + _State_allocator _St_alloc(_My_alloc); + + using _Deleter_allocator = _Rebind_alloc_t<_Alloc, _State_deleter_v2>; + _Deleter_allocator _Del_alloc(_My_alloc); + + _Derived* _Ptr = static_cast<_Derived*>(_State); + + _STD _Delete_plain_internal(_St_alloc, _Ptr); + _STD _Delete_plain_internal(_Del_alloc, this); +} + +template +_Associated_state<_Ty>* _State_deleter_v2<_Ty, _Derived, _Alloc>::_Move_clone(_Associated_state<_Ty>&& _Src) { + return _STD _Make_packaged_state<_Derived>(static_cast<_Derived&&>(_Src)._Get_fn(), _My_alloc); +} template class _Deferred_async_state : public _Packaged_state<_Rx()> { @@ -1185,9 +1221,6 @@ private: _Promise _MyPromise; }; -template -struct uses_allocator, _Alloc> : true_type {}; - _EXPORT_STD template void swap(promise<_Ty>& _Left, promise<_Ty>& _Right) noexcept { _Left.swap(_Right); @@ -1211,21 +1244,19 @@ public: template , packaged_task>, int> = 0> explicit packaged_task(_Fty2&& _Fnarg) : _MyPromise(new _MyStateType(_STD forward<_Fty2>(_Fnarg))) { static_assert(_Is_invocable_r<_Ret, decay_t<_Fty2>&, _ArgTypes...>::value, - "The function object must be callable with _ArgTypes... and return _Ret (N4988 [futures.task.members]/3)."); + "The function object must be callable with _ArgTypes... and return _Ret (N5008 [futures.task.members]/3)."); } packaged_task(packaged_task&&) noexcept = default; packaged_task& operator=(packaged_task&&) noexcept = default; -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT template , packaged_task>, int> = 0> - packaged_task(allocator_arg_t, const _Alloc& _Al, _Fty2&& _Fnarg) + explicit packaged_task(allocator_arg_t, const _Alloc& _Al, _Fty2&& _Fnarg) : _MyPromise(_STD _Make_packaged_state<_MyStateType>(_STD forward<_Fty2>(_Fnarg), _Al)) { static_assert(_Is_invocable_r<_Ret, decay_t<_Fty2>&, _ArgTypes...>::value, - "The function object must be callable with _ArgTypes... and return _Ret (N4140 [futures.task.members]/2)."); + "The function object must be callable with _ArgTypes... and return _Ret (N5008 [futures.task.members]/3)."); } -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT ~packaged_task() noexcept { _MyPromise._Get_state()._Abandon(); @@ -1270,7 +1301,20 @@ public: void reset() { // reset to newly constructed state _MyStateManagerType& _State_mgr = _MyPromise._Get_state_for_set(); _MyStateType& _MyState = *static_cast<_MyStateType*>(_State_mgr._Ptr()); - _MyPromiseType _New_promise(new _MyStateType(_STD move(_MyState)._Get_fn())); + + const auto _New_state_ptr = [&_MyState]() -> _Associated_state<_P_arg_type_t<_Ret>>* { +#ifdef _CPPRTTI // TRANSITION, ABI, should not rely on RTTI + using _Clonable_deleter_t = _Clonable_deleter_base<_P_arg_type_t<_Ret>>; + if (const auto _Clonable_deleter = dynamic_cast<_Clonable_deleter_t*>(_MyState._Get_deleter())) { + return _Clonable_deleter->_Move_clone(_STD move(_MyState)); + } else { + return new _MyStateType(_STD move(_MyState)._Get_fn()); + } +#else // ^^^ defined(_CPPRTTI) / !defined(_CPPRTTI) vvv + return new _MyStateType(_STD move(_MyState)._Get_fn()); +#endif // ^^^ !defined(_CPPRTTI) ^^^ + }(); + _MyPromiseType _New_promise{_New_state_ptr}; _MyPromise._Get_state()._Abandon(); _MyPromise._Swap(_New_promise); } @@ -1294,11 +1338,6 @@ template packaged_task(_Fx) -> packaged_task::type>; #endif // _HAS_CXX17 -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT -template -struct uses_allocator, _Alloc> : true_type {}; -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT - _EXPORT_STD template void swap(packaged_task<_Ty>& _Left, packaged_task<_Ty>& _Right) noexcept { _Left.swap(_Right); diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 4d8f0a2d76b..04821b3b252 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -82,6 +82,7 @@ // P2968R2 Make std::ignore A First-Class Object // P3323R1 Forbid atomic, Specify atomic_ref // (for atomic) +// P3503R3 Make Type-Erased Allocator Use In promise And packaged_task Consistent // _HAS_CXX17 directly controls: // P0005R4 not_fn() @@ -152,8 +153,6 @@ // P0298R3 std::byte // P0302R1 Removing Allocator Support In std::function // LWG-2385 function::assign allocator argument doesn't make sense -// LWG-2921 packaged_task and type-erased allocators -// LWG-2976 Dangling uses_allocator specialization for packaged_task // The non-Standard std::tr1 namespace and TR1-only machinery // Enforcement of matching allocator value_types @@ -1004,8 +1003,6 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect // P0302R1 Removing Allocator Support In std::function // LWG-2385 function::assign allocator argument doesn't make sense -// LWG-2921 packaged_task and type-erased allocators -// LWG-2976 Dangling uses_allocator specialization for packaged_task #ifndef _HAS_FUNCTION_ALLOCATOR_SUPPORT #define _HAS_FUNCTION_ALLOCATOR_SUPPORT (!_HAS_CXX17) #endif // !defined(_HAS_FUNCTION_ALLOCATOR_SUPPORT) diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index d5f9274662e..4c7da11e490 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -138,6 +138,10 @@ std/language.support/support.limits/support.limits.general/cstring.version.compi # libc++ has not implemented P3323R1: "Forbid atomic, Specify atomic_ref" std/atomics/atomics.ref/member_types.compile.pass.cpp FAIL +# libc++ has not implemented P3503R3 "Make Type-Erased Allocator Use In promise And packaged_task Consistent" +std/thread/futures/futures.promise/uses_allocator.pass.cpp FAIL +std/thread/futures/futures.task/futures.task.members/ctor2.compile.pass.cpp FAIL + # Various bogosity (LLVM-D141004), warning C6011: Dereferencing NULL pointer # Note: The :1 (ASan) configuration doesn't run static analysis. std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/construct_pair_const_lvalue_pair.pass.cpp:0 FAIL diff --git a/tests/std/test.lst b/tests/std/test.lst index 666f255aae7..d774c5055cd 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -709,6 +709,7 @@ tests\P2693R1_text_formatting_header_thread tests\P2693R1_text_formatting_stacktrace tests\P2693R1_text_formatting_thread_id tests\P3107R5_enabled_specializations +tests\P3503R3_packaged_task_promise_with_allocator tests\VSO_0000000_allocator_propagation tests\VSO_0000000_any_calling_conventions tests\VSO_0000000_c_math_functions diff --git a/tests/std/tests/Dev10_561430_list_and_tree_leaks/test.cpp b/tests/std/tests/Dev10_561430_list_and_tree_leaks/test.cpp index 99952f17f12..fc31bbf4b3d 100644 --- a/tests/std/tests/Dev10_561430_list_and_tree_leaks/test.cpp +++ b/tests/std/tests/Dev10_561430_list_and_tree_leaks/test.cpp @@ -157,7 +157,6 @@ int main() { f.get(); } -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT { packaged_task pt(allocator_arg, Mallocator(), [] { return 1234; }); @@ -200,7 +199,6 @@ int main() { f.get(); } -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT #endif // _M_CEE_PURE assert(g_mallocs == 0); diff --git a/tests/std/tests/Dev11_0920385_list_sort_allocator/test.cpp b/tests/std/tests/Dev11_0920385_list_sort_allocator/test.cpp index d876c1d2be6..d8b35089d2d 100644 --- a/tests/std/tests/Dev11_0920385_list_sort_allocator/test.cpp +++ b/tests/std/tests/Dev11_0920385_list_sort_allocator/test.cpp @@ -254,7 +254,6 @@ int main() { f.get(); } -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT { packaged_task pt(allocator_arg, alloc, [] { return 1234; }); future f = pt.get_future(); @@ -276,7 +275,6 @@ int main() { pt(); f.get(); } -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT #endif // _M_CEE_PURE test_DevDiv_1119194(); diff --git a/tests/std/tests/GH_000140_adl_proof_construction/test.compile.pass.cpp b/tests/std/tests/GH_000140_adl_proof_construction/test.compile.pass.cpp index 57e78b559cc..a8041454dd5 100644 --- a/tests/std/tests/GH_000140_adl_proof_construction/test.compile.pass.cpp +++ b/tests/std/tests/GH_000140_adl_proof_construction/test.compile.pass.cpp @@ -130,13 +130,11 @@ void test_packaged_task() { packaged_task{validating_identity{}}; packaged_task{validating_large_identity{}}; -#if !_HAS_CXX17 packaged_task{allocator_arg, adl_proof_allocator{}, simple_identity{}}; packaged_task{allocator_arg, adl_proof_allocator{}, simple_large_identity{}}; packaged_task{allocator_arg, adl_proof_allocator{}, validating_identity{}}; packaged_task{allocator_arg, adl_proof_allocator{}, validating_large_identity{}}; -#endif // !_HAS_CXX17 } void test_promise() { diff --git a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/env.lst b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/env.lst new file mode 100644 index 00000000000..f141421b292 --- /dev/null +++ b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\impure_matrix.lst diff --git a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp new file mode 100644 index 00000000000..f3af76e0d08 --- /dev/null +++ b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp @@ -0,0 +1,250 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +#define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__) + +using namespace std; + +ptrdiff_t global_allocation_count = 0; +ptrdiff_t global_deallocation_count = 0; + +template +struct global_counting_allocator { + using value_type = T; + + global_counting_allocator() = default; + template + constexpr global_counting_allocator(global_counting_allocator) noexcept {} + + T* allocate(size_t n) { + auto p = allocator{}.allocate(n); + ++global_allocation_count; + return p; + } + + void deallocate(T* p, size_t n) { + allocator{}.deallocate(p, n); + ++global_deallocation_count; + } + + template + friend constexpr bool operator==(global_counting_allocator, global_counting_allocator) noexcept { + return true; + } +#if !_HAS_CXX20 + template + friend constexpr bool operator!=(global_counting_allocator, global_counting_allocator) noexcept { + return false; + } +#endif // !_HAS_CXX20 +}; + +template +void test_implicit_conversion(T) noexcept; + +template +constexpr bool is_copy_list_initializable_impl = false; +template +constexpr bool + is_copy_list_initializable_impl({declval()...})), T, Args...> = true; + +template +constexpr bool is_copy_list_initializable = is_copy_list_initializable_impl; + +template +struct large_functor { + unsigned char dummy_[64]{}; + + template + R operator()(Args&&...) const { + return R{}; + } +}; +template +struct large_functor { + unsigned char dummy_[64]{}; + + template + R& operator()(Args&&...) const { + static R r{}; + return r; + } +}; +template <> +struct large_functor { + unsigned char dummy_[64]{}; + + template + void operator()(Args&&...) const {} +}; + +STATIC_ASSERT(is_constructible_v>); +STATIC_ASSERT(is_constructible_v, const allocator_arg_t&, const allocator&>); +STATIC_ASSERT(is_constructible_v, const allocator_arg_t&, const global_counting_allocator&>); + +STATIC_ASSERT(is_copy_list_initializable>); +STATIC_ASSERT(is_copy_list_initializable, const allocator_arg_t&, const allocator&>); +STATIC_ASSERT(is_copy_list_initializable, const allocator_arg_t&, const global_counting_allocator&>); + +STATIC_ASSERT(is_constructible_v>); +STATIC_ASSERT( + is_constructible_v, const allocator_arg_t&, const allocator&, large_functor>); +STATIC_ASSERT(is_constructible_v, const allocator_arg_t&, const global_counting_allocator&, + large_functor>); + +STATIC_ASSERT(is_copy_list_initializable>); +STATIC_ASSERT(!is_copy_list_initializable, const allocator_arg_t&, const allocator&, + large_functor>); +STATIC_ASSERT( + !is_copy_list_initializable, const allocator_arg_t&, const global_counting_allocator&>); + +STATIC_ASSERT(!uses_allocator_v, allocator>); +STATIC_ASSERT(!uses_allocator_v, allocator>); +STATIC_ASSERT(!uses_allocator_v, allocator>); +STATIC_ASSERT(!uses_allocator_v, global_counting_allocator>); +STATIC_ASSERT(!uses_allocator_v, global_counting_allocator>); +STATIC_ASSERT(!uses_allocator_v, global_counting_allocator>); + +STATIC_ASSERT(!uses_allocator_v, allocator>); +STATIC_ASSERT(!uses_allocator_v, allocator>); +STATIC_ASSERT(!uses_allocator_v, allocator>); +STATIC_ASSERT(!uses_allocator_v, global_counting_allocator>); +STATIC_ASSERT(!uses_allocator_v, global_counting_allocator>); +STATIC_ASSERT(!uses_allocator_v, global_counting_allocator>); + +template +void test_construction_promise_in_tuple() { + using prom = promise; + + { + tuple t{allocator_arg, allocator{}}; + (void) t; + } + { + const auto old_alloc_cnt = global_allocation_count; + const auto old_dealloc_cnt = global_deallocation_count; + { + tuple t{allocator_arg, global_counting_allocator{}}; + (void) t; + + assert(global_allocation_count == old_alloc_cnt); + assert(global_deallocation_count == old_dealloc_cnt); + } + assert(global_allocation_count == old_alloc_cnt); + assert(global_deallocation_count == old_dealloc_cnt); + } + { + tuple t{allocator_arg, allocator{}, prom{}}; + (void) t; + } + { + const auto old_alloc_cnt = global_allocation_count; + const auto old_dealloc_cnt = global_deallocation_count; + { + tuple t{allocator_arg, global_counting_allocator{}, prom{}}; + (void) t; + + assert(global_allocation_count == old_alloc_cnt); + assert(global_deallocation_count == old_dealloc_cnt); + } + assert(global_allocation_count == old_alloc_cnt); + assert(global_deallocation_count == old_dealloc_cnt); + } +} + +template +void test_construction_packaged_task_in_tuple() { + using pt = packaged_task; + + { + tuple t{allocator_arg, allocator{}}; + (void) t; + } + { + const auto old_alloc_cnt = global_allocation_count; + const auto old_dealloc_cnt = global_deallocation_count; + { + tuple t{allocator_arg, global_counting_allocator{}}; + (void) t; + + assert(global_allocation_count == old_alloc_cnt); + assert(global_deallocation_count == old_dealloc_cnt); + } + assert(global_allocation_count == old_alloc_cnt); + assert(global_deallocation_count == old_dealloc_cnt); + } + { + tuple t{allocator_arg, allocator{}, pt{}}; + (void) t; + } + { + + const auto old_alloc_cnt = global_allocation_count; + const auto old_dealloc_cnt = global_deallocation_count; + { + tuple t{allocator_arg, global_counting_allocator{}, pt{}}; + (void) t; + + assert(global_allocation_count == old_alloc_cnt); + assert(global_deallocation_count == old_dealloc_cnt); + } + assert(global_allocation_count == old_alloc_cnt); + assert(global_deallocation_count == old_dealloc_cnt); + } +} + +struct alloc_dealloc_count_result { + ptrdiff_t alloc_count; + ptrdiff_t dealloc_count; +}; + +template +void test_construction_packaged_task_reset(F2&& functor) { + assert(global_allocation_count == global_deallocation_count); + + const auto old_cnts = [&functor]() -> alloc_dealloc_count_result { + packaged_task pt{allocator_arg, global_counting_allocator{}, forward(functor)}; + + const auto alloc_cnt1 = global_allocation_count; + const auto dealloc_cnt1 = global_deallocation_count; + + pt.reset(); + + const auto alloc_cnt2 = global_allocation_count; + const auto dealloc_cnt2 = global_deallocation_count; + +#ifdef _CPPRTTI // TRNAISTION, ABI, correct behavior should not rely on RTTI + assert(alloc_cnt2 > alloc_cnt1); +#else // ^^^ defined(_CPPRTTI) / !defined(_CPPRTTI) vvv + assert(alloc_cnt2 == alloc_cnt1); +#endif // ^^^ !defined(_CPPRTTI) ^^^ + assert(dealloc_cnt2 > dealloc_cnt1); + + return {alloc_cnt2, dealloc_cnt2}; + }(); + + assert(global_allocation_count == old_cnts.alloc_count); + assert(global_deallocation_count > old_cnts.dealloc_count); + assert(global_allocation_count == global_deallocation_count); +} + +int main() { + test_construction_promise_in_tuple(); + test_construction_promise_in_tuple(); + test_construction_promise_in_tuple(); + + test_construction_packaged_task_in_tuple(); + test_construction_packaged_task_in_tuple(); + test_construction_packaged_task_in_tuple(); + + test_construction_packaged_task_reset(large_functor{}); + test_construction_packaged_task_reset(large_functor{}); + test_construction_packaged_task_reset(large_functor{}); +} diff --git a/tests/std/tests/VSO_0000000_instantiate_iterators_misc/test.compile.pass.cpp b/tests/std/tests/VSO_0000000_instantiate_iterators_misc/test.compile.pass.cpp index 23fb0ef0f53..62ee79b2e56 100644 --- a/tests/std/tests/VSO_0000000_instantiate_iterators_misc/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0000000_instantiate_iterators_misc/test.compile.pass.cpp @@ -557,11 +557,9 @@ void future_test() { // GH-321: ": packaged_task can't be constructed from a move-only lambda" packaged_task pt2([uptr = unique_ptr{}]() { (void) uptr; }); -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT packaged_task pta(allocator_arg, allocator{}, []() {}); // GH-321: ": packaged_task can't be constructed from a move-only lambda" packaged_task pta2(allocator_arg, allocator{}, [uptr = unique_ptr{}]() { (void) uptr; }); -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT swap_test(pt); @@ -570,9 +568,7 @@ void future_test() { TRAIT_V(uses_allocator, promise, allocator); -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT TRAIT_V(uses_allocator, packaged_task, allocator); -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT } #endif // _M_CEE_PURE diff --git a/tests/tr1/tests/future/test.cpp b/tests/tr1/tests/future/test.cpp index 1c8ca7b7299..91ef34f9624 100644 --- a/tests/tr1/tests/future/test.cpp +++ b/tests/tr1/tests/future/test.cpp @@ -330,7 +330,7 @@ static void do_test_promise_allocator() { // check that allocator is managed cor static void test_promise_allocator() { // test promise with allocator typedef STD uses_allocator, STD allocator> promise_uses_allocator; - CHECK_INT(promise_uses_allocator::value, true); + CHECK_INT(promise_uses_allocator::value, false); do_test_promise_allocator(); do_test_promise_allocator(); @@ -836,10 +836,8 @@ static void test_packaged_task_core_v() { // test core of packaged_task for func } static void test_packaged_task_allocator() { // test packaged_task with allocator -#if _HAS_FUNCTION_ALLOCATOR_SUPPORT typedef STD uses_allocator, STD allocator> packaged_task_uses_allocator; - CHECK_INT(packaged_task_uses_allocator::value, true); -#endif // _HAS_FUNCTION_ALLOCATOR_SUPPORT + CHECK_INT(packaged_task_uses_allocator::value, false); } static void test_packaged_task() { // do basic tests for packaged_task From c5e603cb5d619752f6216d9655ae375a66b8a304 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Sat, 11 Oct 2025 09:39:50 +0800 Subject: [PATCH 2/9] Update citations to refer to WG21-N5014 --- stl/inc/future | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/future b/stl/inc/future index 8c0a8385ad0..a053876cb39 100644 --- a/stl/inc/future +++ b/stl/inc/future @@ -1244,7 +1244,7 @@ public: template , packaged_task>, int> = 0> explicit packaged_task(_Fty2&& _Fnarg) : _MyPromise(new _MyStateType(_STD forward<_Fty2>(_Fnarg))) { static_assert(_Is_invocable_r<_Ret, decay_t<_Fty2>&, _ArgTypes...>::value, - "The function object must be callable with _ArgTypes... and return _Ret (N5008 [futures.task.members]/3)."); + "The function object must be callable with _ArgTypes... and return _Ret (N5014 [futures.task.members]/3)."); } packaged_task(packaged_task&&) noexcept = default; @@ -1255,7 +1255,7 @@ public: explicit packaged_task(allocator_arg_t, const _Alloc& _Al, _Fty2&& _Fnarg) : _MyPromise(_STD _Make_packaged_state<_MyStateType>(_STD forward<_Fty2>(_Fnarg), _Al)) { static_assert(_Is_invocable_r<_Ret, decay_t<_Fty2>&, _ArgTypes...>::value, - "The function object must be callable with _ArgTypes... and return _Ret (N5008 [futures.task.members]/3)."); + "The function object must be callable with _ArgTypes... and return _Ret (N5014 [futures.task.members]/3)."); } ~packaged_task() noexcept { From 54d9b58e32ee14df33b0c615c00c85aeabf8e738 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 22 Oct 2025 00:33:18 -0700 Subject: [PATCH 3/9] Fix typos. --- stl/inc/future | 2 +- .../tests/P3503R3_packaged_task_promise_with_allocator/test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/future b/stl/inc/future index a053876cb39..93ece97fa3d 100644 --- a/stl/inc/future +++ b/stl/inc/future @@ -586,7 +586,7 @@ _Associated_state<_Ty>* _Make_associated_state(const _Alloc& _Al) { template _Pack_state* _Make_packaged_state(_Fty2&& _Fnarg, const _Alloc& _Al) { // construct a _Packaged_state object with an allocator from an rvalue function object -#ifdef _CPPRTTI // TRANSITON, ABI, should not rely on RTTI +#ifdef _CPPRTTI // TRANSITION, ABI, should not rely on RTTI using _Delty = _State_deleter_v2; #else // ^^^ defined(_CPPRTTI) / !defined(_CPPRTTI) vvv using _Delty = _State_deleter; diff --git a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp index f3af76e0d08..37b4019e821 100644 --- a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp +++ b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp @@ -220,7 +220,7 @@ void test_construction_packaged_task_reset(F2&& functor) { const auto alloc_cnt2 = global_allocation_count; const auto dealloc_cnt2 = global_deallocation_count; -#ifdef _CPPRTTI // TRNAISTION, ABI, correct behavior should not rely on RTTI +#ifdef _CPPRTTI // TRANSITION, ABI, correct behavior should not rely on RTTI assert(alloc_cnt2 > alloc_cnt1); #else // ^^^ defined(_CPPRTTI) / !defined(_CPPRTTI) vvv assert(alloc_cnt2 == alloc_cnt1); From f230ab0fa9aa43637b9937995cc2612f0735c25e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 22 Oct 2025 00:42:32 -0700 Subject: [PATCH 4/9] Fix Standard citations. --- stl/inc/future | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/future b/stl/inc/future index 93ece97fa3d..456dd477050 100644 --- a/stl/inc/future +++ b/stl/inc/future @@ -1244,7 +1244,7 @@ public: template , packaged_task>, int> = 0> explicit packaged_task(_Fty2&& _Fnarg) : _MyPromise(new _MyStateType(_STD forward<_Fty2>(_Fnarg))) { static_assert(_Is_invocable_r<_Ret, decay_t<_Fty2>&, _ArgTypes...>::value, - "The function object must be callable with _ArgTypes... and return _Ret (N5014 [futures.task.members]/3)."); + "The function object must be callable with _ArgTypes... and return _Ret (N5014 [futures.task.members]/4)."); } packaged_task(packaged_task&&) noexcept = default; @@ -1255,7 +1255,7 @@ public: explicit packaged_task(allocator_arg_t, const _Alloc& _Al, _Fty2&& _Fnarg) : _MyPromise(_STD _Make_packaged_state<_MyStateType>(_STD forward<_Fty2>(_Fnarg), _Al)) { static_assert(_Is_invocable_r<_Ret, decay_t<_Fty2>&, _ArgTypes...>::value, - "The function object must be callable with _ArgTypes... and return _Ret (N5014 [futures.task.members]/3)."); + "The function object must be callable with _ArgTypes... and return _Ret (N5014 [futures.task.members]/4)."); } ~packaged_task() noexcept { From e79217de1e485c924ffd8f6e42f598609c437246 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 22 Oct 2025 00:59:23 -0700 Subject: [PATCH 5/9] Include headers. --- .../tests/P3503R3_packaged_task_promise_with_allocator/test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp index 37b4019e821..8bf036e8cfb 100644 --- a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp +++ b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__) From 2b5fe8fdab86839712d25224ebcbada09a40a709 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 22 Oct 2025 01:01:50 -0700 Subject: [PATCH 6/9] Style: Avoid unnecessary aliases. --- .../test.cpp | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp index 8bf036e8cfb..33e0846b207 100644 --- a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp +++ b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp @@ -122,17 +122,15 @@ STATIC_ASSERT(!uses_allocator_v, global_counting_allocator template void test_construction_promise_in_tuple() { - using prom = promise; - { - tuple t{allocator_arg, allocator{}}; + tuple> t{allocator_arg, allocator{}}; (void) t; } { const auto old_alloc_cnt = global_allocation_count; const auto old_dealloc_cnt = global_deallocation_count; { - tuple t{allocator_arg, global_counting_allocator{}}; + tuple> t{allocator_arg, global_counting_allocator{}}; (void) t; assert(global_allocation_count == old_alloc_cnt); @@ -142,14 +140,14 @@ void test_construction_promise_in_tuple() { assert(global_deallocation_count == old_dealloc_cnt); } { - tuple t{allocator_arg, allocator{}, prom{}}; + tuple> t{allocator_arg, allocator{}, promise{}}; (void) t; } { const auto old_alloc_cnt = global_allocation_count; const auto old_dealloc_cnt = global_deallocation_count; { - tuple t{allocator_arg, global_counting_allocator{}, prom{}}; + tuple> t{allocator_arg, global_counting_allocator{}, promise{}}; (void) t; assert(global_allocation_count == old_alloc_cnt); @@ -162,17 +160,15 @@ void test_construction_promise_in_tuple() { template void test_construction_packaged_task_in_tuple() { - using pt = packaged_task; - { - tuple t{allocator_arg, allocator{}}; + tuple> t{allocator_arg, allocator{}}; (void) t; } { const auto old_alloc_cnt = global_allocation_count; const auto old_dealloc_cnt = global_deallocation_count; { - tuple t{allocator_arg, global_counting_allocator{}}; + tuple> t{allocator_arg, global_counting_allocator{}}; (void) t; assert(global_allocation_count == old_alloc_cnt); @@ -182,7 +178,7 @@ void test_construction_packaged_task_in_tuple() { assert(global_deallocation_count == old_dealloc_cnt); } { - tuple t{allocator_arg, allocator{}, pt{}}; + tuple> t{allocator_arg, allocator{}, packaged_task{}}; (void) t; } { @@ -190,7 +186,7 @@ void test_construction_packaged_task_in_tuple() { const auto old_alloc_cnt = global_allocation_count; const auto old_dealloc_cnt = global_deallocation_count; { - tuple t{allocator_arg, global_counting_allocator{}, pt{}}; + tuple> t{allocator_arg, global_counting_allocator{}, packaged_task{}}; (void) t; assert(global_allocation_count == old_alloc_cnt); From 567a67c88cf19098022214b77303fc9885cd51b7 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 22 Oct 2025 01:07:22 -0700 Subject: [PATCH 7/9] Style: Take `const&` when rebinding. --- .../tests/P3503R3_packaged_task_promise_with_allocator/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp index 33e0846b207..04fc13c2591 100644 --- a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp +++ b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp @@ -22,7 +22,7 @@ struct global_counting_allocator { global_counting_allocator() = default; template - constexpr global_counting_allocator(global_counting_allocator) noexcept {} + constexpr global_counting_allocator(const global_counting_allocator&) noexcept {} T* allocate(size_t n) { auto p = allocator{}.allocate(n); From a9b6504dd11ade0eb7733d15e67d8b3e89c0a0f4 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 22 Oct 2025 01:14:08 -0700 Subject: [PATCH 8/9] Test fix: Add missing `large_functor`. --- .../P3503R3_packaged_task_promise_with_allocator/test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp index 04fc13c2591..5ccfbd6a401 100644 --- a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp +++ b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp @@ -103,8 +103,8 @@ STATIC_ASSERT(is_constructible_v, const allocator_arg_t&, c STATIC_ASSERT(is_copy_list_initializable>); STATIC_ASSERT(!is_copy_list_initializable, const allocator_arg_t&, const allocator&, large_functor>); -STATIC_ASSERT( - !is_copy_list_initializable, const allocator_arg_t&, const global_counting_allocator&>); +STATIC_ASSERT(!is_copy_list_initializable, const allocator_arg_t&, + const global_counting_allocator&, large_functor>); STATIC_ASSERT(!uses_allocator_v, allocator>); STATIC_ASSERT(!uses_allocator_v, allocator>); From 03420215b8e2330a4689dfd9e1e335a346e05e92 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 22 Oct 2025 01:21:59 -0700 Subject: [PATCH 9/9] Remove newline. --- .../tests/P3503R3_packaged_task_promise_with_allocator/test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp index 5ccfbd6a401..ae11870ee7a 100644 --- a/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp +++ b/tests/std/tests/P3503R3_packaged_task_promise_with_allocator/test.cpp @@ -182,7 +182,6 @@ void test_construction_packaged_task_in_tuple() { (void) t; } { - const auto old_alloc_cnt = global_allocation_count; const auto old_dealloc_cnt = global_deallocation_count; {