From d81c4ebde92db9e62cda1bc2d267779a31bca48e Mon Sep 17 00:00:00 2001 From: Steve Gerbino Date: Thu, 23 Jul 2026 15:29:04 +0200 Subject: [PATCH] fix(run_async): keep allocator arguments out of handler deduction A pointer to a derived memory resource deduced as an exact-match completion handler, beating the memory_resource* overloads that need a derived-to-base conversion, then failing to instantiate when the pointer was invoked. The same hijack applied to the second handler position and, once constrained naively, made allocator values ambiguous against the value-allocator overloads. Constrain every deduced handler position with a RunAsyncHandler concept that excludes the types meaningful to the other parameters: memory resource pointers, stop tokens, and allocators. --- include/boost/capy/ex/run_async.hpp | 17 +++++++++++++++++ test/unit/ex/run_async.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/include/boost/capy/ex/run_async.hpp b/include/boost/capy/ex/run_async.hpp index 70fd8ed54..3951e552a 100644 --- a/include/boost/capy/ex/run_async.hpp +++ b/include/boost/capy/ex/run_async.hpp @@ -34,6 +34,19 @@ namespace boost { namespace capy { namespace detail { +/** Match types usable as `run_async` completion handlers. + + Excludes the types meaningful to the other `run_async` parameters, + so a stop token, memory resource pointer, or allocator argument + selects its dedicated overload by conversion instead of deducing + as an exact-match handler. +*/ +template +concept RunAsyncHandler = + !std::is_convertible_v && + !std::is_convertible_v && + !Allocator; + /// Function pointer type for type-erased frame deallocation. using dealloc_fn = void(*)(void*, std::size_t); @@ -517,6 +530,7 @@ run_async(Ex ex) @see executor */ template + requires detail::RunAsyncHandler

[[nodiscard]] auto run_async(Ex ex, H1 h1) { @@ -560,6 +574,7 @@ run_async(Ex ex, H1 h1) @see executor */ template + requires (detail::RunAsyncHandler

&& detail::RunAsyncHandler

) [[nodiscard]] auto run_async(Ex ex, H1 h1, H2 h2) { @@ -626,6 +641,7 @@ run_async(Ex ex, std::stop_token st) @see executor */ template + requires detail::RunAsyncHandler

[[nodiscard]] auto run_async(Ex ex, std::stop_token st, H1 h1) { @@ -653,6 +669,7 @@ run_async(Ex ex, std::stop_token st, H1 h1) @see executor */ template + requires (detail::RunAsyncHandler

&& detail::RunAsyncHandler

) [[nodiscard]] auto run_async(Ex ex, std::stop_token st, H1 h1, H2 h2) { diff --git a/test/unit/ex/run_async.cpp b/test/unit/ex/run_async.cpp index e54e26f15..3536f7f86 100644 --- a/test/unit/ex/run_async.cpp +++ b/test/unit/ex/run_async.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -507,6 +508,33 @@ struct run_async_test BOOST_TEST(result); } + static task + set_flag(bool& flag) + { + flag = true; + co_return; + } + + void + testDerivedResourcePointer() + { + int dispatch_count = 0; + sync_executor d(dispatch_count); + std::pmr::monotonic_buffer_resource arena; + bool ran = false; + + run_async(d, &arena)(set_flag(ran)); + BOOST_TEST(ran); + + ran = false; + run_async(d, std::stop_token{}, &arena)(set_flag(ran)); + BOOST_TEST(ran); + + ran = false; + run_async(d, &arena, [&] { ran = true; })(returns_void()); + BOOST_TEST(ran); + } + //---------------------------------------------------------- // Sync Dispatcher //---------------------------------------------------------- @@ -775,6 +803,7 @@ struct run_async_test // Allocator Propagation testAllocatorPropagation(); + testDerivedResourcePointer(); // Sync Dispatcher testSyncDispatcherBasic();