From 77d09d001d781d82d8e4cf34141118deea8dc51c Mon Sep 17 00:00:00 2001 From: Christopher Kohlhoff Date: Wed, 29 Oct 2025 22:56:44 +1100 Subject: [PATCH] Add dispatch, post and defer overloads that run a function before completion. --- include/boost/asio/defer.hpp | 361 +++++++++++++++++- include/boost/asio/detail/bind_handler.hpp | 8 + include/boost/asio/detail/handler_work.hpp | 2 +- include/boost/asio/detail/initiate_defer.hpp | 36 +- .../boost/asio/detail/initiate_dispatch.hpp | 36 +- include/boost/asio/detail/initiate_post.hpp | 36 +- include/boost/asio/detail/type_traits.hpp | 2 + include/boost/asio/detail/work_dispatcher.hpp | 200 +++++++++- include/boost/asio/detail/wrapped_handler.hpp | 35 +- include/boost/asio/dispatch.hpp | 340 ++++++++++++++++- .../experimental/detail/channel_operation.hpp | 18 +- include/boost/asio/post.hpp | 343 ++++++++++++++++- test/defer.cpp | 210 +++++++++- test/dispatch.cpp | 210 +++++++++- test/post.cpp | 210 +++++++++- 15 files changed, 1921 insertions(+), 126 deletions(-) diff --git a/include/boost/asio/defer.hpp b/include/boost/asio/defer.hpp index d51a5877..8bf0477a 100644 --- a/include/boost/asio/defer.hpp +++ b/include/boost/asio/defer.hpp @@ -59,10 +59,11 @@ namespace asio { * The function call operator of @c Init: * * @li Obtains the handler's associated executor object @c ex of type @c Ex by - * performing @code auto ex = get_associated_executor(handler); @endcode + * performing + * @code auto ex = get_associated_executor(completion_handler); @endcode * * @li Obtains the handler's associated allocator object @c alloc by performing - * @code auto alloc = get_associated_allocator(handler); @endcode + * @code auto alloc = get_associated_allocator(completion_handler); @endcode * * @li If execution::is_executor::value is true, performs * @code prefer( @@ -80,7 +81,7 @@ namespace asio { * @code void() @endcode */ template -auto defer(NullaryToken&& token) +inline auto defer(NullaryToken&& token) -> decltype( async_initiate( declval(), token)) @@ -125,10 +126,11 @@ auto defer(NullaryToken&& token) * The function call operator of @c Init: * * @li Obtains the handler's associated executor object @c ex1 of type @c Ex1 by - * performing @code auto ex1 = get_associated_executor(handler, ex); @endcode + * performing + * @code auto ex1 = get_associated_executor(completion_handler, ex); @endcode * * @li Obtains the handler's associated allocator object @c alloc by performing - * @code auto alloc = get_associated_allocator(handler); @endcode + * @code auto alloc = get_associated_allocator(completion_handler); @endcode * * @li If execution::is_executor::value is true, constructs a * function object @c f with a member @c executor_ that is initialised with @@ -147,14 +149,14 @@ auto defer(NullaryToken&& token) * work_.get_executor().dispatch(std::move(handler_), a); * work_.reset(); @endcode * - * @li If execution::is_executor::value is true, performs + * @li If execution::is_executor::value is true, performs * @code prefer( * require(ex, execution::blocking.never), * execution::relationship.continuation, * execution::allocator(alloc) * ).execute(std::move(f)); @endcode * - * @li If execution::is_executor::value is false, performs + * @li If execution::is_executor::value is false, performs * @code ex.defer(std::move(f), alloc); @endcode * * @par Completion Signature @@ -163,9 +165,8 @@ auto defer(NullaryToken&& token) template > -auto defer(const Executor& ex, - NullaryToken&& token - = default_completion_token_t(), +inline auto defer(const Executor& ex, + NullaryToken&& token = default_completion_token_t(), constraint_t< (execution::is_executor::value && can_require::value) @@ -173,10 +174,12 @@ auto defer(const Executor& ex, > = 0) -> decltype( async_initiate( - declval>(), token)) + declval>(), + token, detail::empty_work_function())) { return async_initiate( - detail::initiate_defer_with_executor(ex), token); + detail::initiate_defer_with_executor(ex), + token, detail::empty_work_function()); } /// Submits a completion token or function object for execution. @@ -195,21 +198,343 @@ auto defer(const Executor& ex, template > -auto defer(ExecutionContext& ctx, - NullaryToken&& token - = default_completion_token_t(), +inline auto defer(ExecutionContext& ctx, + NullaryToken&& token = default_completion_token_t< + typename ExecutionContext::executor_type>(), constraint_t< is_convertible::value > = 0) -> decltype( async_initiate( declval>(), token)) + typename ExecutionContext::executor_type>>(), + token, detail::empty_work_function())) { return async_initiate( detail::initiate_defer_with_executor< - typename ExecutionContext::executor_type>( - ctx.get_executor()), token); + typename ExecutionContext::executor_type>(ctx.get_executor()), + token, detail::empty_work_function()); +} + + +/// Submits a function to be run on a specified target executor, and after +/// completion submits the completion handler. +/** + * This function submits a function object for execution on the specified + * executor. The function object is queued for execution, and is never called + * from the current thread prior to returning from defer(). After the + * submitted function completes, the completion handler is dispatched to run on + * its associated executor. + * + * The use of @c defer(), rather than @ref post(), indicates the caller's + * preference that the executor defer the queueing of the function object. This + * may allow the executor to optimise queueing for cases when the function + * object represents a continuation of the current call context. + * + * @param function A nullary function to be executed on the target executor. + * + * @param ex The target executor. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(); @endcode + * + * @returns This function returns async_initiate(Init{ex}, token, forward(function)), where @c Init is + * a function object type defined as: + * + * @code class Init + * { + * public: + * using executor_type = Executor; + * explicit Init(const Executor& ex) : ex_(ex) {} + * executor_type get_executor() const noexcept { return ex_; } + * template + * void operator()(CompletionHandler&& completion_handler, + * Function&& function) const; + * private: + * Executor ex_; // exposition only + * }; @endcode + * + * The function call operator of @c Init: + * + * @li Obtains the handler's associated executor object @c ex1 of type @c Ex1 by + * performing + * @code auto ex1 = get_associated_executor(completion_handler, ex); @endcode + * + * @li Obtains the handler's associated allocator object @c alloc by performing + * @code auto alloc = get_associated_allocator(completion_handler); @endcode + * + * @li If execution::is_executor::value is true, constructs a + * function object wrapper @c f with a member @c executor_ that is initialised + * with prefer(ex1, execution::outstanding_work.tracked), a member @c + * function_ that is a decay-copy of @c function, a member @c handler_ that is a + * decay-copy of @c completion_handler, and a function call operator that + * performs: + * @code std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * prefer(executor_, execution::allocator(a)).execute(std::move(handler_)); + * @endcode + * + * @li If execution::is_executor::value is false, constructs a + * function object wrapper @c f with a member @c work_ that is initialised with + * make_work_guard(ex1), a member @c function_ that is a decay-copy of + * @c function, a member @c handler_ that is a decay-copy of @c + * completion_handler, and a function call operator that performs: + * @code std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * work_.get_executor().dispatch(std::move(handler_), a); + * work_.reset(); @endcode + * + * @li If execution::is_executor::value is true, performs + * @code prefer( + * require(ex, execution::blocking.never), + * execution::relationship.fork, + * execution::allocator(alloc) + * ).execute(std::move(f)); @endcode + * + * @li If execution::is_executor::value is false, performs + * @code ex.defer(std::move(f), alloc); @endcode + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void() @endcode + */ +template > +inline auto defer(Function&& function, const Executor& ex, + NullaryToken&& token = default_completion_token_t(), + constraint_t< + is_void()>>::value + > = 0, + constraint_t< + (execution::is_executor::value + && can_require::value) + || is_executor::value + > = 0) + -> decltype( + async_initiate( + declval>(), + token, static_cast(function))) +{ + return async_initiate( + detail::initiate_defer_with_executor(ex), + token, static_cast(function)); +} + +/// Submits a function to be run on a specified target executor, and passes the +/// result to a completion handler. +/** + * This function submits a function object for execution on the specified + * executor. The function object is queued for execution, and is never called + * from the current thread prior to returning from defer(). After the + * submitted function completes, the completion handler is dispatched along with + * the function's result, to run on its associated executor. + * + * The use of @c defer(), rather than @ref post(), indicates the caller's + * preference that the executor defer the queueing of the function object. This + * may allow the executor to optimise queueing for cases when the function + * object represents a continuation of the current call context. + * + * @param function A nullary function to be executed on the target executor. + * + * @param ex The target executor. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(decay_t()>>); @endcode + * + * @returns This function returns async_initiate(Init{ex}, token), where @c Init is a function object type + * defined as: + * + * @code class Init + * { + * public: + * using executor_type = Executor; + * explicit Init(const Executor& ex) : ex_(ex) {} + * executor_type get_executor() const noexcept { return ex_; } + * template + * void operator()(CompletionHandler&& completion_handler, + * Function&& function) const; + * private: + * Executor ex_; // exposition only + * }; @endcode + * + * The function call operator of @c Init: + * + * @li Obtains the handler's associated executor object @c ex1 of type @c Ex1 by + * performing + * @code auto ex1 = get_associated_executor(completion_handler, ex); @endcode + * + * @li Obtains the handler's associated allocator object @c alloc by performing + * @code auto alloc = get_associated_allocator(completion_handler); @endcode + * + * @li If execution::is_executor::value is true, constructs a + * function object wrapper @c f with a member @c executor_ that is initialised + * with prefer(ex1, execution::outstanding_work.tracked), a member @c + * function_ that is a decay-copy of @c function, a member @c handler_ that is a + * decay-copy of @c completion_handler, and a function call operator that + * performs: + * @code auto result = std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * prefer(executor_, execution::allocator(a)).execute( + * std::bind(std::move(handler_), std::move(result))); + * @endcode + * + * @li If execution::is_executor::value is false, constructs a + * function object wrapper @c f with a member @c work_ that is initialised with + * make_work_guard(ex1), a member @c function_ that is a decay-copy of + * @c function, a member @c handler_ that is a decay-copy of @c + * completion_handler, and a function call operator that performs: + * @code auto result = std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * work_.get_executor().dispatch( + * std::bind(std::move(handler_), std::move(result)), a); + * work_.reset(); @endcode + * + * @li If execution::is_executor::value is true, performs + * @code prefer( + * require(ex, execution::blocking.never), + * execution::relationship.fork, + * execution::allocator(alloc) + * ).execute(std::move(f)); @endcode + * + * @li If execution::is_executor::value is false, performs + * @code ex.defer(std::move(f), alloc); @endcode + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void(decay_t()>>) @endcode + */ +template ()>>)) CompletionToken + = default_completion_token_t> +inline auto defer(Function&& function, const Executor& ex, + CompletionToken&& token = default_completion_token_t(), + constraint_t< + !is_void()>>::value + > = 0, + constraint_t< + (execution::is_executor::value + && can_require::value) + || is_executor::value + > = 0) + -> decltype( + async_initiate)>( + declval>(), + token, static_cast(function))) +{ + return async_initiate)>( + detail::initiate_defer_with_executor(ex), + token, static_cast(function)); +} + +/// Submits a function to be run on a specified execution context, and after +/// completion submits the completion handler. +/** + * @param function A nullary function to be executed on the target executor. + * + * @param ctx An execution context, from which the target executor is obtained. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(); @endcode + * + * @returns defer(forward(function), ctx.get_executor(), + * forward(token)). + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void() @endcode + */ +template > +inline auto defer(Function&& function, ExecutionContext& ctx, + NullaryToken&& token = default_completion_token_t< + typename ExecutionContext::executor_type>(), + constraint_t< + is_void()>>::value + > = 0, + constraint_t< + is_convertible::value + > = 0) + -> decltype( + async_initiate( + declval>(), + token, static_cast(function))) +{ + return async_initiate( + detail::initiate_defer_with_executor< + typename ExecutionContext::executor_type>(ctx.get_executor()), + token, static_cast(function)); +} + +/// Submits a function to be run on a specified execution context, and passes +/// the result to a completion handler. +/** + * @param function A nullary function to be executed on the target executor. + * + * @param ctx An execution context, from which the target executor is obtained. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(); @endcode + * + * @returns defer(forward(function), ctx.get_executor(), + * forward(token)). + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void(decay_t()>>) @endcode + */ +template ()>>)) CompletionToken + = default_completion_token_t> +inline auto defer(Function&& function, ExecutionContext& ctx, + CompletionToken&& token = default_completion_token_t< + typename ExecutionContext::executor_type>(), + constraint_t< + !is_void()>>::value + > = 0, + constraint_t< + is_convertible::value + > = 0) + -> decltype( + async_initiate)>( + declval>(), + token, static_cast(function))) +{ + return async_initiate)>( + detail::initiate_defer_with_executor< + typename ExecutionContext::executor_type>(ctx.get_executor()), + token, static_cast(function)); } } // namespace asio diff --git a/include/boost/asio/detail/bind_handler.hpp b/include/boost/asio/detail/bind_handler.hpp index df03387f..03cce347 100644 --- a/include/boost/asio/detail/bind_handler.hpp +++ b/include/boost/asio/detail/bind_handler.hpp @@ -490,6 +490,14 @@ public: Arg1 arg1_; }; +template +inline move_binder1, decay_t> move_bind_handler( + Handler&& handler, Arg1&& arg1) +{ + return move_binder1, decay_t>(0, + static_cast(handler), static_cast(arg1)); +} + template inline bool asio_handler_is_continuation( move_binder1* this_handler) diff --git a/include/boost/asio/detail/handler_work.hpp b/include/boost/asio/detail/handler_work.hpp index ff6fb7b4..80b3770b 100644 --- a/include/boost/asio/detail/handler_work.hpp +++ b/include/boost/asio/detail/handler_work.hpp @@ -497,7 +497,7 @@ public: handler, *static_cast(io_ex)); (initiate_dispatch_with_executor(immediate_ex))( - static_cast(function)); + static_cast(function), empty_work_function()); } private: diff --git a/include/boost/asio/detail/initiate_defer.hpp b/include/boost/asio/detail/initiate_defer.hpp index e3336238..3da0a6da 100644 --- a/include/boost/asio/detail/initiate_defer.hpp +++ b/include/boost/asio/detail/initiate_defer.hpp @@ -92,15 +92,16 @@ public: return ex_; } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&&, enable_if_t< execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - !detail::is_work_dispatcher_required< + !is_work_dispatcher_required< + decay_t, decay_t, Executor >::value @@ -118,21 +119,23 @@ public: static_cast(handler))); } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&& function, enable_if_t< execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - detail::is_work_dispatcher_required< + is_work_dispatcher_required< + decay_t, decay_t, Executor >::value >* = 0) const { typedef decay_t handler_t; + typedef decay_t function_t; typedef associated_executor_t handler_ex_t; handler_ex_t handler_ex((get_associated_executor)(handler, ex_)); @@ -145,19 +148,21 @@ public: execution::relationship.continuation, execution::allocator(alloc) ).execute( - detail::work_dispatcher( + work_dispatcher( + static_cast(function), static_cast(handler), handler_ex)); } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&&, enable_if_t< !execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - !detail::is_work_dispatcher_required< + !is_work_dispatcher_required< + decay_t, decay_t, Executor >::value @@ -170,21 +175,23 @@ public: static_cast(handler)), alloc); } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&& function, enable_if_t< !execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - detail::is_work_dispatcher_required< + is_work_dispatcher_required< + decay_t, decay_t, Executor >::value >* = 0) const { typedef decay_t handler_t; + typedef decay_t function_t; typedef associated_executor_t handler_ex_t; handler_ex_t handler_ex((get_associated_executor)(handler, ex_)); @@ -192,7 +199,8 @@ public: associated_allocator_t alloc( (get_associated_allocator)(handler)); - ex_.defer(detail::work_dispatcher( + ex_.defer(work_dispatcher( + static_cast(function), static_cast(handler), handler_ex), alloc); } diff --git a/include/boost/asio/detail/initiate_dispatch.hpp b/include/boost/asio/detail/initiate_dispatch.hpp index aaaa165b..d4cf2dfc 100644 --- a/include/boost/asio/detail/initiate_dispatch.hpp +++ b/include/boost/asio/detail/initiate_dispatch.hpp @@ -86,15 +86,16 @@ public: return ex_; } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&&, enable_if_t< execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - !detail::is_work_dispatcher_required< + !is_work_dispatcher_required< + decay_t, decay_t, Executor >::value @@ -108,21 +109,23 @@ public: static_cast(handler))); } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&& function, enable_if_t< execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - detail::is_work_dispatcher_required< + is_work_dispatcher_required< + decay_t, decay_t, Executor >::value >* = 0) const { typedef decay_t handler_t; + typedef decay_t function_t; typedef associated_executor_t handler_ex_t; handler_ex_t handler_ex((get_associated_executor)(handler, ex_)); @@ -131,19 +134,21 @@ public: (get_associated_allocator)(handler)); boost::asio::prefer(ex_, execution::allocator(alloc)).execute( - detail::work_dispatcher( + work_dispatcher( + static_cast(function), static_cast(handler), handler_ex)); } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&&, enable_if_t< !execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - !detail::is_work_dispatcher_required< + !is_work_dispatcher_required< + decay_t, decay_t, Executor >::value @@ -156,21 +161,23 @@ public: static_cast(handler)), alloc); } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&& function, enable_if_t< !execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - detail::is_work_dispatcher_required< + is_work_dispatcher_required< + decay_t, decay_t, Executor >::value >* = 0) const { typedef decay_t handler_t; + typedef decay_t function_t; typedef associated_executor_t handler_ex_t; handler_ex_t handler_ex((get_associated_executor)(handler, ex_)); @@ -178,7 +185,8 @@ public: associated_allocator_t alloc( (get_associated_allocator)(handler)); - ex_.dispatch(detail::work_dispatcher( + ex_.dispatch(work_dispatcher( + static_cast(function), static_cast(handler), handler_ex), alloc); } diff --git a/include/boost/asio/detail/initiate_post.hpp b/include/boost/asio/detail/initiate_post.hpp index a7adb1f6..0e140fbb 100644 --- a/include/boost/asio/detail/initiate_post.hpp +++ b/include/boost/asio/detail/initiate_post.hpp @@ -92,15 +92,16 @@ public: return ex_; } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&&, enable_if_t< execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - !detail::is_work_dispatcher_required< + !is_work_dispatcher_required< + decay_t, decay_t, Executor >::value @@ -118,21 +119,23 @@ public: static_cast(handler))); } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&& function, enable_if_t< execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - detail::is_work_dispatcher_required< + is_work_dispatcher_required< + decay_t, decay_t, Executor >::value >* = 0) const { typedef decay_t handler_t; + typedef decay_t function_t; typedef associated_executor_t handler_ex_t; handler_ex_t handler_ex((get_associated_executor)(handler, ex_)); @@ -145,19 +148,21 @@ public: execution::relationship.fork, execution::allocator(alloc) ).execute( - detail::work_dispatcher( + work_dispatcher( + static_cast(function), static_cast(handler), handler_ex)); } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&&, enable_if_t< !execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - !detail::is_work_dispatcher_required< + !is_work_dispatcher_required< + decay_t, decay_t, Executor >::value @@ -170,21 +175,23 @@ public: static_cast(handler)), alloc); } - template - void operator()(CompletionHandler&& handler, + template + void operator()(CompletionHandler&& handler, Function&& function, enable_if_t< !execution::is_executor< conditional_t >::value >* = 0, enable_if_t< - detail::is_work_dispatcher_required< + is_work_dispatcher_required< + decay_t, decay_t, Executor >::value >* = 0) const { typedef decay_t handler_t; + typedef decay_t function_t; typedef associated_executor_t handler_ex_t; handler_ex_t handler_ex((get_associated_executor)(handler, ex_)); @@ -192,7 +199,8 @@ public: associated_allocator_t alloc( (get_associated_allocator)(handler)); - ex_.post(detail::work_dispatcher( + ex_.post(work_dispatcher( + static_cast(function), static_cast(handler), handler_ex), alloc); } diff --git a/include/boost/asio/detail/type_traits.hpp b/include/boost/asio/detail/type_traits.hpp index 14eefa42..a5e8b6c2 100644 --- a/include/boost/asio/detail/type_traits.hpp +++ b/include/boost/asio/detail/type_traits.hpp @@ -110,6 +110,8 @@ using std::is_scalar; using std::is_unsigned; +using std::is_void; + using std::remove_cv; template diff --git a/include/boost/asio/detail/work_dispatcher.hpp b/include/boost/asio/detail/work_dispatcher.hpp index 06d54142..7b7f92a4 100644 --- a/include/boost/asio/detail/work_dispatcher.hpp +++ b/include/boost/asio/detail/work_dispatcher.hpp @@ -33,13 +33,30 @@ namespace boost { namespace asio { namespace detail { -template +struct empty_work_function +{ + void operator()() const noexcept + { + } +}; + +template +struct work_result +{ + typedef decay_t()>> type; +}; + +template +using work_result_t = typename work_result::type; + +template struct is_work_dispatcher_required : true_type { }; template -struct is_work_dispatcher_required -class work_dispatcher +template >::value, + bool IsClass = is_class::value> +class work_dispatcher_function +{ + Function function_; + +public: + template + work_dispatcher_function(F&& function) + : function_(static_cast(function)) + { + } + + work_dispatcher_function(const work_dispatcher_function& other) + : function_(other.function_) + { + } + + work_dispatcher_function(work_dispatcher_function&& other) + : function_(static_cast(other.function_)) + { + } + + template + auto bind_result(Handler&& handler) + -> decltype( + boost::asio::detail::move_bind_handler( + static_cast(handler), + static_cast(function_)())) + { + return boost::asio::detail::move_bind_handler( + static_cast(handler), + static_cast(function_)()); + } +}; + +template +class work_dispatcher_function : Function { public: - template - work_dispatcher(CompletionHandler&& handler, + template + work_dispatcher_function(F&& function) + : Function(static_cast(function)) + { + } + + work_dispatcher_function(const work_dispatcher_function& other) + : Function(static_cast(other)) + { + } + + work_dispatcher_function(work_dispatcher_function&& other) + : Function(static_cast(other)) + { + } + + template + auto bind_result(Handler&& handler) + -> decltype( + boost::asio::detail::move_bind_handler( + static_cast(handler), + static_cast(*static_cast(this))())) + { + return boost::asio::detail::move_bind_handler( + static_cast(handler), + static_cast(*static_cast(this))()); + } +}; + +template +class work_dispatcher_function +{ + Function function_; + +public: + template + work_dispatcher_function(F&& function) + : function_(static_cast(function)) + { + } + + work_dispatcher_function(const work_dispatcher_function& other) + : function_(other.function_) + { + } + + work_dispatcher_function(work_dispatcher_function&& other) + : function_(static_cast(other.function_)) + { + } + + template + auto bind_result(Handler&& handler) + -> decltype(boost::asio::detail::bind_handler( + static_cast(handler))) + { + static_cast(function_)(); + return boost::asio::detail::bind_handler( + static_cast(handler)); + } +}; + +template +class work_dispatcher_function : Function +{ +public: + template + work_dispatcher_function(F&& function) + : Function(static_cast(function)) + { + } + + work_dispatcher_function(const work_dispatcher_function& other) + : Function(static_cast(other)) + { + } + + work_dispatcher_function(work_dispatcher_function&& other) + : Function(static_cast(other)) + { + } + + template + auto bind_result(Handler&& handler) + -> decltype(boost::asio::detail::bind_handler( + static_cast(handler))) + { + static_cast(*static_cast(this))(); + return boost::asio::detail::bind_handler( + static_cast(handler)); + } +}; + +template +class work_dispatcher : work_dispatcher_function +{ +public: + template + work_dispatcher(F&& function, CompletionHandler&& handler, const Executor& handler_ex) - : handler_(static_cast(handler)), + : work_dispatcher_function(static_cast(function)), + handler_(static_cast(handler)), executor_(boost::asio::prefer(handler_ex, execution::outstanding_work.tracked)) { } work_dispatcher(const work_dispatcher& other) - : handler_(other.handler_), + : work_dispatcher_function(other), + handler_(other.handler_), executor_(other.executor_) { } work_dispatcher(work_dispatcher&& other) - : handler_(static_cast(other.handler_)), + : work_dispatcher_function( + static_cast&&>(other)), + handler_(static_cast(other.handler_)), executor_(static_cast(other.executor_)) { } @@ -79,8 +235,7 @@ public: { associated_allocator_t alloc((get_associated_allocator)(handler_)); boost::asio::prefer(executor_, execution::allocator(alloc)).execute( - boost::asio::detail::bind_handler( - static_cast(handler_))); + this->bind_result(static_cast(handler_))); } private: @@ -96,26 +251,32 @@ private: #if !defined(BOOST_ASIO_NO_TS_EXECUTORS) -template -class work_dispatcher +class work_dispatcher::value>> + : work_dispatcher_function { public: - template - work_dispatcher(CompletionHandler&& handler, const Executor& handler_ex) - : work_(handler_ex), + template + work_dispatcher(F&& function, CompletionHandler&& handler, + const Executor& handler_ex) + : work_dispatcher_function(static_cast(function)), + work_(handler_ex), handler_(static_cast(handler)) { } work_dispatcher(const work_dispatcher& other) - : work_(other.work_), + : work_dispatcher_function(other), + work_(other.work_), handler_(other.handler_) { } work_dispatcher(work_dispatcher&& other) - : work_(static_cast&&>(other.work_)), + : work_dispatcher_function( + static_cast&&>(*this)), + work_(static_cast&&>(other.work_)), handler_(static_cast(other.handler_)) { } @@ -124,8 +285,7 @@ public: { associated_allocator_t alloc((get_associated_allocator)(handler_)); work_.get_executor().dispatch( - boost::asio::detail::bind_handler( - static_cast(handler_)), alloc); + this->bind_result(static_cast(handler_)), alloc); work_.reset(); } diff --git a/include/boost/asio/detail/wrapped_handler.hpp b/include/boost/asio/detail/wrapped_handler.hpp index dbbd73ad..5b530b9b 100644 --- a/include/boost/asio/detail/wrapped_handler.hpp +++ b/include/boost/asio/detail/wrapped_handler.hpp @@ -100,55 +100,62 @@ public: void operator()() { detail::initiate_dispatch_with_executor( - this->get_executor())(static_cast(handler_)); + this->get_executor())(static_cast(handler_), + empty_work_function()); } void operator()() const { detail::initiate_dispatch_with_executor( - this->get_executor())(handler_); + this->get_executor())(handler_, empty_work_function()); } template void operator()(const Arg1& arg1) { detail::initiate_dispatch_with_executor( - this->get_executor())(detail::bind_handler(handler_, arg1)); + this->get_executor())(detail::bind_handler(handler_, arg1), + empty_work_function()); } template void operator()(const Arg1& arg1) const { detail::initiate_dispatch_with_executor( - this->get_executor())(detail::bind_handler(handler_, arg1)); + this->get_executor())(detail::bind_handler(handler_, arg1), + empty_work_function()); } template void operator()(const Arg1& arg1, const Arg2& arg2) { detail::initiate_dispatch_with_executor( - this->get_executor())(detail::bind_handler(handler_, arg1, arg2)); + this->get_executor())(detail::bind_handler(handler_, arg1, arg2), + empty_work_function()); } template void operator()(const Arg1& arg1, const Arg2& arg2) const { detail::initiate_dispatch_with_executor( - this->get_executor())(detail::bind_handler(handler_, arg1, arg2)); + this->get_executor())(detail::bind_handler(handler_, arg1, arg2), + empty_work_function()); } template void operator()(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3) { detail::initiate_dispatch_with_executor( - this->get_executor())(detail::bind_handler(handler_, arg1, arg2, arg3)); + this->get_executor())(detail::bind_handler(handler_, arg1, arg2, arg3), + empty_work_function()); } template void operator()(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3) const { detail::initiate_dispatch_with_executor( - this->get_executor())(detail::bind_handler(handler_, arg1, arg2, arg3)); + this->get_executor())(detail::bind_handler(handler_, arg1, arg2, arg3), + empty_work_function()); } template @@ -157,7 +164,8 @@ public: { detail::initiate_dispatch_with_executor( this->get_executor())( - detail::bind_handler(handler_, arg1, arg2, arg3, arg4)); + detail::bind_handler(handler_, arg1, arg2, arg3, arg4), + empty_work_function()); } template @@ -166,7 +174,8 @@ public: { detail::initiate_dispatch_with_executor( this->get_executor())( - detail::bind_handler(handler_, arg1, arg2, arg3, arg4)); + detail::bind_handler(handler_, arg1, arg2, arg3, arg4), + empty_work_function()); } template ( this->get_executor())( - detail::bind_handler(handler_, arg1, arg2, arg3, arg4, arg5)); + detail::bind_handler(handler_, arg1, arg2, arg3, arg4, arg5), + empty_work_function()); } template ( this->get_executor())( - detail::bind_handler(handler_, arg1, arg2, arg3, arg4, arg5)); + detail::bind_handler(handler_, arg1, arg2, arg3, arg4, arg5), + empty_work_function()); } //private: diff --git a/include/boost/asio/dispatch.hpp b/include/boost/asio/dispatch.hpp index 3556caaf..62dcfef5 100644 --- a/include/boost/asio/dispatch.hpp +++ b/include/boost/asio/dispatch.hpp @@ -52,10 +52,11 @@ namespace asio { * The function call operator of @c Init: * * @li Obtains the handler's associated executor object @c ex of type @c Ex by - * performing @code auto ex = get_associated_executor(handler); @endcode + * performing + * @code auto ex = get_associated_executor(completion_handler); @endcode * * @li Obtains the handler's associated allocator object @c alloc by performing - * @code auto alloc = get_associated_allocator(handler); @endcode + * @code auto alloc = get_associated_allocator(completion_handler); @endcode * * @li If execution::is_executor::value is true, performs * @code prefer(ex, execution::allocator(alloc)).execute( @@ -110,10 +111,11 @@ inline auto dispatch(NullaryToken&& token) * The function call operator of @c Init: * * @li Obtains the handler's associated executor object @c ex1 of type @c Ex1 by - * performing @code auto ex1 = get_associated_executor(handler, ex); @endcode + * performing + * @code auto ex1 = get_associated_executor(completion_handler, ex); @endcode * * @li Obtains the handler's associated allocator object @c alloc by performing - * @code auto alloc = get_associated_allocator(handler); @endcode + * @code auto alloc = get_associated_allocator(completion_handler); @endcode * * @li If execution::is_executor::value is true, constructs a * function object @c f with a member @c executor_ that is initialised with @@ -132,10 +134,10 @@ inline auto dispatch(NullaryToken&& token) * work_.get_executor().dispatch(std::move(handler_), a); * work_.reset(); @endcode * - * @li If execution::is_executor::value is true, performs + * @li If execution::is_executor::value is true, performs * @code prefer(ex, execution::allocator(alloc)).execute(std::move(f)); @endcode * - * @li If execution::is_executor::value is false, performs + * @li If execution::is_executor::value is false, performs * @code ex.dispatch(std::move(f), alloc); @endcode * * @par Completion Signature @@ -151,10 +153,12 @@ inline auto dispatch(const Executor& ex, > = 0) -> decltype( async_initiate( - declval>(), token)) + declval>(), + token, detail::empty_work_function())) { return async_initiate( - detail::initiate_dispatch_with_executor(ex), token); + detail::initiate_dispatch_with_executor(ex), + token, detail::empty_work_function()); } /// Submits a completion token or function object for execution. @@ -165,8 +169,7 @@ inline auto dispatch(const Executor& ex, * completion handler. The function signature of the completion handler must be: * @code void handler(); @endcode * - * @returns dispatch(ctx.get_executor(), - * forward(token)). + * @returns dispatch(ctx.get_executor(), forward(token)). * * @par Completion Signature * @code void() @endcode @@ -183,12 +186,323 @@ inline auto dispatch(ExecutionContext& ctx, -> decltype( async_initiate( declval>(), token)) + typename ExecutionContext::executor_type>>(), + token, detail::empty_work_function())) { return async_initiate( detail::initiate_dispatch_with_executor< - typename ExecutionContext::executor_type>( - ctx.get_executor()), token); + typename ExecutionContext::executor_type>(ctx.get_executor()), + token, detail::empty_work_function()); +} + +/// Submits a function to be run on a specified target executor, and after +/// completion submits the completion handler. +/** + * This function submits a function object for execution using the specified + * executor. The function object may be called from the current thread prior to + * returning from dispatch(). Otherwise, it is queued for execution. + * After the submitted function completes, the completion handler is dispatched + * to run on its associated executor. + * + * @param function A nullary function to be executed on the target executor. + * + * @param ex The target executor. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(); @endcode + * + * @returns This function returns async_initiate(Init{ex}, token, forward(function)), where @c Init is + * a function object type defined as: + * + * @code class Init + * { + * public: + * using executor_type = Executor; + * explicit Init(const Executor& ex) : ex_(ex) {} + * executor_type get_executor() const noexcept { return ex_; } + * template + * void operator()(CompletionHandler&& completion_handler, + * Function&& function) const; + * private: + * Executor ex_; // exposition only + * }; @endcode + * + * The function call operator of @c Init: + * + * @li Obtains the handler's associated executor object @c ex1 of type @c Ex1 by + * performing + * @code auto ex1 = get_associated_executor(completion_handler, ex); @endcode + * + * @li Obtains the handler's associated allocator object @c alloc by performing + * @code auto alloc = get_associated_allocator(completion_handler); @endcode + * + * @li If execution::is_executor::value is true, constructs a + * function object wrapper @c f with a member @c executor_ that is initialised + * with prefer(ex1, execution::outstanding_work.tracked), a member @c + * function_ that is a decay-copy of @c function, a member @c handler_ that is a + * decay-copy of @c completion_handler, and a function call operator that + * performs: + * @code std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * prefer(executor_, execution::allocator(a)).execute(std::move(handler_)); + * @endcode + * + * @li If execution::is_executor::value is false, constructs a + * function object wrapper @c f with a member @c work_ that is initialised with + * make_work_guard(ex1), a member @c function_ that is a decay-copy of + * @c function, a member @c handler_ that is a decay-copy of @c + * completion_handler, and a function call operator that performs: + * @code std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * work_.get_executor().dispatch(std::move(handler_), a); + * work_.reset(); @endcode + * + * @li If execution::is_executor::value is true, performs + * @code prefer( + * require(ex, execution::blocking.never), + * execution::relationship.fork, + * execution::allocator(alloc) + * ).execute(std::move(f)); @endcode + * + * @li If execution::is_executor::value is false, performs + * @code ex.dispatch(std::move(f), alloc); @endcode + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void() @endcode + */ +template > +inline auto dispatch(Function&& function, const Executor& ex, + NullaryToken&& token = default_completion_token_t(), + constraint_t< + is_void()>>::value + > = 0, + constraint_t< + (execution::is_executor::value + && can_require::value) + || is_executor::value + > = 0) + -> decltype( + async_initiate( + declval>(), + token, static_cast(function))) +{ + return async_initiate( + detail::initiate_dispatch_with_executor(ex), + token, static_cast(function)); +} + +/// Submits a function to be run on a specified target executor, and passes the +/// result to a completion handler. +/** + * This function submits a function object for execution using the specified + * executor. The function object may be called from the current thread prior to + * returning from dispatch(). Otherwise, it is queued for execution. + * After the submitted function completes, the completion handler is dispatched + * along with the function's result, to run on its associated executor. + * + * @param function A nullary function to be executed on the target executor. + * + * @param ex The target executor. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(decay_t()>>); @endcode + * + * @returns This function returns async_initiate(Init{ex}, token), where @c Init is a function object type + * defined as: + * + * @code class Init + * { + * public: + * using executor_type = Executor; + * explicit Init(const Executor& ex) : ex_(ex) {} + * executor_type get_executor() const noexcept { return ex_; } + * template + * void operator()(CompletionHandler&& completion_handler, + * Function&& function) const; + * private: + * Executor ex_; // exposition only + * }; @endcode + * + * The function call operator of @c Init: + * + * @li Obtains the handler's associated executor object @c ex1 of type @c Ex1 by + * performing + * @code auto ex1 = get_associated_executor(completion_handler, ex); @endcode + * + * @li Obtains the handler's associated allocator object @c alloc by performing + * @code auto alloc = get_associated_allocator(completion_handler); @endcode + * + * @li If execution::is_executor::value is true, constructs a + * function object wrapper @c f with a member @c executor_ that is initialised + * with prefer(ex1, execution::outstanding_work.tracked), a member @c + * function_ that is a decay-copy of @c function, a member @c handler_ that is a + * decay-copy of @c completion_handler, and a function call operator that + * performs: + * @code auto result = std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * prefer(executor_, execution::allocator(a)).execute( + * std::bind(std::move(handler_), std::move(result))); + * @endcode + * + * @li If execution::is_executor::value is false, constructs a + * function object wrapper @c f with a member @c work_ that is initialised with + * make_work_guard(ex1), a member @c function_ that is a decay-copy of + * @c function, a member @c handler_ that is a decay-copy of @c + * completion_handler, and a function call operator that performs: + * @code auto result = std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * work_.get_executor().dispatch( + * std::bind(std::move(handler_), std::move(result)), a); + * work_.reset(); @endcode + * + * @li If execution::is_executor::value is true, performs + * @code prefer( + * require(ex, execution::blocking.never), + * execution::relationship.fork, + * execution::allocator(alloc) + * ).execute(std::move(f)); @endcode + * + * @li If execution::is_executor::value is false, performs + * @code ex.dispatch(std::move(f), alloc); @endcode + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void(decay_t()>>) @endcode + */ +template ()>>)) CompletionToken + = default_completion_token_t> +inline auto dispatch(Function&& function, const Executor& ex, + CompletionToken&& token = default_completion_token_t(), + constraint_t< + !is_void()>>::value + > = 0, + constraint_t< + (execution::is_executor::value + && can_require::value) + || is_executor::value + > = 0) + -> decltype( + async_initiate)>( + declval>(), + token, static_cast(function))) +{ + return async_initiate)>( + detail::initiate_dispatch_with_executor(ex), + token, static_cast(function)); +} + +/// Submits a function to be run on a specified execution context, and after +/// completion submits the completion handler. +/** + * @param function A nullary function to be executed on the target executor. + * + * @param ctx An execution context, from which the target executor is obtained. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(); @endcode + * + * @returns dispatch(forward(function), ctx.get_executor(), + * forward(token)). + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void() @endcode + */ +template > +inline auto dispatch(Function&& function, ExecutionContext& ctx, + NullaryToken&& token = default_completion_token_t< + typename ExecutionContext::executor_type>(), + constraint_t< + is_void()>>::value + > = 0, + constraint_t< + is_convertible::value + > = 0) + -> decltype( + async_initiate( + declval>(), + token, static_cast(function))) +{ + return async_initiate( + detail::initiate_dispatch_with_executor< + typename ExecutionContext::executor_type>(ctx.get_executor()), + token, static_cast(function)); +} + +/// Submits a function to be run on a specified execution context, and passes +/// the result to a completion handler. +/** + * @param function A nullary function to be executed on the target executor. + * + * @param ctx An execution context, from which the target executor is obtained. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(); @endcode + * + * @returns dispatch(forward(function), ctx.get_executor(), + * forward(token)). + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void(decay_t()>>) @endcode + */ +template ()>>)) CompletionToken + = default_completion_token_t> +inline auto dispatch(Function&& function, ExecutionContext& ctx, + CompletionToken&& token = default_completion_token_t< + typename ExecutionContext::executor_type>(), + constraint_t< + !is_void()>>::value + > = 0, + constraint_t< + is_convertible::value + > = 0) + -> decltype( + async_initiate)>( + declval>(), + token, static_cast(function))) +{ + return async_initiate)>( + detail::initiate_dispatch_with_executor< + typename ExecutionContext::executor_type>(ctx.get_executor()), + token, static_cast(function)); } } // namespace asio diff --git a/include/boost/asio/experimental/detail/channel_operation.hpp b/include/boost/asio/experimental/detail/channel_operation.hpp index 37ade8a1..a1be1635 100644 --- a/include/boost/asio/experimental/detail/channel_operation.hpp +++ b/include/boost/asio/experimental/detail/channel_operation.hpp @@ -109,7 +109,8 @@ public: void post(const IoExecutor& io_exec, Function& function, Handler&) { (boost::asio::detail::initiate_post_with_executor(io_exec))( - static_cast(function)); + static_cast(function), + boost::asio::detail::empty_work_function()); } template @@ -269,8 +270,10 @@ public: immediate_ex_type immediate_ex = (get_associated_immediate_executor)( handler, base1_type::get_executor()); - (boost::asio::detail::initiate_dispatch_with_executor( - immediate_ex))(static_cast(function)); + (boost::asio::detail::initiate_dispatch_with_executor< + immediate_ex_type>(immediate_ex))( + static_cast(function), + boost::asio::detail::empty_work_function()); } template @@ -288,7 +291,8 @@ public: (boost::asio::detail::initiate_post_with_executor< typename base1_type::executor_type>( base1_type::get_executor()))( - static_cast(function)); + static_cast(function), + boost::asio::detail::empty_work_function()); } }; @@ -333,8 +337,10 @@ public: immediate_ex_type immediate_ex = (get_associated_immediate_executor)( handler, base1_type::get_executor()); - (boost::asio::detail::initiate_dispatch_with_executor( - immediate_ex))(static_cast(function)); + (boost::asio::detail::initiate_dispatch_with_executor< + immediate_ex_type>(immediate_ex))( + static_cast(function), + boost::asio::detail::empty_work_function()); } template diff --git a/include/boost/asio/post.hpp b/include/boost/asio/post.hpp index 64845694..0ceadc05 100644 --- a/include/boost/asio/post.hpp +++ b/include/boost/asio/post.hpp @@ -57,10 +57,11 @@ namespace asio { * The function call operator of @c Init: * * @li Obtains the handler's associated executor object @c ex of type @c Ex by - * performing @code auto ex = get_associated_executor(handler); @endcode + * performing + * @code auto ex = get_associated_executor(completion_handler); @endcode * * @li Obtains the handler's associated allocator object @c alloc by performing - * @code auto alloc = get_associated_allocator(handler); @endcode + * @code auto alloc = get_associated_allocator(completion_handler); @endcode * * @li If execution::is_executor::value is true, performs * @code prefer( @@ -121,10 +122,11 @@ inline auto post(NullaryToken&& token) * The function call operator of @c Init: * * @li Obtains the handler's associated executor object @c ex1 of type @c Ex1 by - * performing @code auto ex1 = get_associated_executor(handler, ex); @endcode + * performing + * @code auto ex1 = get_associated_executor(completion_handler, ex); @endcode * * @li Obtains the handler's associated allocator object @c alloc by performing - * @code auto alloc = get_associated_allocator(handler); @endcode + * @code auto alloc = get_associated_allocator(completion_handler); @endcode * * @li If execution::is_executor::value is true, constructs a * function object @c f with a member @c executor_ that is initialised with @@ -143,14 +145,14 @@ inline auto post(NullaryToken&& token) * work_.get_executor().dispatch(std::move(handler_), a); * work_.reset(); @endcode * - * @li If execution::is_executor::value is true, performs + * @li If execution::is_executor::value is true, performs * @code prefer( * require(ex, execution::blocking.never), * execution::relationship.fork, * execution::allocator(alloc) * ).execute(std::move(f)); @endcode * - * @li If execution::is_executor::value is false, performs + * @li If execution::is_executor::value is false, performs * @code ex.post(std::move(f), alloc); @endcode * * @par Completion Signature @@ -168,10 +170,12 @@ inline auto post(const Executor& ex, > = 0) -> decltype( async_initiate( - declval>(), token)) + declval>(), + token, detail::empty_work_function())) { return async_initiate( - detail::initiate_post_with_executor(ex), token); + detail::initiate_post_with_executor(ex), + token, detail::empty_work_function()); } /// Submits a completion token or function object for execution. @@ -199,12 +203,329 @@ inline auto post(ExecutionContext& ctx, -> decltype( async_initiate( declval>(), token)) + typename ExecutionContext::executor_type>>(), + token, detail::empty_work_function())) { return async_initiate( detail::initiate_post_with_executor< - typename ExecutionContext::executor_type>( - ctx.get_executor()), token); + typename ExecutionContext::executor_type>(ctx.get_executor()), + token, detail::empty_work_function()); +} + +/// Submits a function to be run on a specified target executor, and after +/// completion submits the completion handler. +/** + * This function submits a function object for execution on the specified + * executor. The function object is queued for execution, and is never called + * from the current thread prior to returning from post(). After the + * submitted function completes, the completion handler is dispatched to run on + * its associated executor. + * + * The use of @c post(), rather than @ref defer(), indicates the caller's + * preference that the function object be eagerly queued for execution. + * + * @param function A nullary function to be executed on the target executor. + * + * @param ex The target executor. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(); @endcode + * + * @returns This function returns async_initiate(Init{ex}, token, forward(function)), where @c Init is + * a function object type defined as: + * + * @code class Init + * { + * public: + * using executor_type = Executor; + * explicit Init(const Executor& ex) : ex_(ex) {} + * executor_type get_executor() const noexcept { return ex_; } + * template + * void operator()(CompletionHandler&& completion_handler, + * Function&& function) const; + * private: + * Executor ex_; // exposition only + * }; @endcode + * + * The function call operator of @c Init: + * + * @li Obtains the handler's associated executor object @c ex1 of type @c Ex1 by + * performing + * @code auto ex1 = get_associated_executor(completion_handler, ex); @endcode + * + * @li Obtains the handler's associated allocator object @c alloc by performing + * @code auto alloc = get_associated_allocator(completion_handler); @endcode + * + * @li If execution::is_executor::value is true, constructs a + * function object wrapper @c f with a member @c executor_ that is initialised + * with prefer(ex1, execution::outstanding_work.tracked), a member @c + * function_ that is a decay-copy of @c function, a member @c handler_ that is a + * decay-copy of @c completion_handler, and a function call operator that + * performs: + * @code std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * prefer(executor_, execution::allocator(a)).execute(std::move(handler_)); + * @endcode + * + * @li If execution::is_executor::value is false, constructs a + * function object wrapper @c f with a member @c work_ that is initialised with + * make_work_guard(ex1), a member @c function_ that is a decay-copy of + * @c function, a member @c handler_ that is a decay-copy of @c + * completion_handler, and a function call operator that performs: + * @code std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * work_.get_executor().dispatch(std::move(handler_), a); + * work_.reset(); @endcode + * + * @li If execution::is_executor::value is true, performs + * @code prefer( + * require(ex, execution::blocking.never), + * execution::relationship.fork, + * execution::allocator(alloc) + * ).execute(std::move(f)); @endcode + * + * @li If execution::is_executor::value is false, performs + * @code ex.post(std::move(f), alloc); @endcode + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void() @endcode + */ +template > +inline auto post(Function&& function, const Executor& ex, + NullaryToken&& token = default_completion_token_t(), + constraint_t< + is_void()>>::value + > = 0, + constraint_t< + (execution::is_executor::value + && can_require::value) + || is_executor::value + > = 0) + -> decltype( + async_initiate( + declval>(), + token, static_cast(function))) +{ + return async_initiate( + detail::initiate_post_with_executor(ex), + token, static_cast(function)); +} + +/// Submits a function to be run on a specified target executor, and passes the +/// result to a completion handler. +/** + * This function submits a function object for execution on the specified + * executor. The function object is queued for execution, and is never called + * from the current thread prior to returning from post(). After the + * submitted function completes, the completion handler is dispatched along with + * the function's result, to run on its associated executor. + * + * The use of @c post(), rather than @ref defer(), indicates the caller's + * preference that the function object be eagerly queued for execution. + * + * @param function A nullary function to be executed on the target executor. + * + * @param ex The target executor. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(decay_t()>>); @endcode + * + * @returns This function returns async_initiate(Init{ex}, token), where @c Init is a function object type + * defined as: + * + * @code class Init + * { + * public: + * using executor_type = Executor; + * explicit Init(const Executor& ex) : ex_(ex) {} + * executor_type get_executor() const noexcept { return ex_; } + * template + * void operator()(CompletionHandler&& completion_handler, + * Function&& function) const; + * private: + * Executor ex_; // exposition only + * }; @endcode + * + * The function call operator of @c Init: + * + * @li Obtains the handler's associated executor object @c ex1 of type @c Ex1 by + * performing + * @code auto ex1 = get_associated_executor(completion_handler, ex); @endcode + * + * @li Obtains the handler's associated allocator object @c alloc by performing + * @code auto alloc = get_associated_allocator(completion_handler); @endcode + * + * @li If execution::is_executor::value is true, constructs a + * function object wrapper @c f with a member @c executor_ that is initialised + * with prefer(ex1, execution::outstanding_work.tracked), a member @c + * function_ that is a decay-copy of @c function, a member @c handler_ that is a + * decay-copy of @c completion_handler, and a function call operator that + * performs: + * @code auto result = std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * prefer(executor_, execution::allocator(a)).execute( + * std::bind(std::move(handler_), std::move(result))); + * @endcode + * + * @li If execution::is_executor::value is false, constructs a + * function object wrapper @c f with a member @c work_ that is initialised with + * make_work_guard(ex1), a member @c function_ that is a decay-copy of + * @c function, a member @c handler_ that is a decay-copy of @c + * completion_handler, and a function call operator that performs: + * @code auto result = std::move(function_)(); + * auto a = get_associated_allocator(handler_); + * work_.get_executor().dispatch( + * std::bind(std::move(handler_), std::move(result)), a); + * work_.reset(); @endcode + * + * @li If execution::is_executor::value is true, performs + * @code prefer( + * require(ex, execution::blocking.never), + * execution::relationship.fork, + * execution::allocator(alloc) + * ).execute(std::move(f)); @endcode + * + * @li If execution::is_executor::value is false, performs + * @code ex.post(std::move(f), alloc); @endcode + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void(decay_t()>>) @endcode + */ +template ()>>)) CompletionToken + = default_completion_token_t> +inline auto post(Function&& function, const Executor& ex, + CompletionToken&& token = default_completion_token_t(), + constraint_t< + !is_void()>>::value + > = 0, + constraint_t< + (execution::is_executor::value + && can_require::value) + || is_executor::value + > = 0) + -> decltype( + async_initiate)>( + declval>(), + token, static_cast(function))) +{ + return async_initiate)>( + detail::initiate_post_with_executor(ex), + token, static_cast(function)); +} + +/// Submits a function to be run on a specified execution context, and after +/// completion submits the completion handler. +/** + * @param function A nullary function to be executed on the target executor. + * + * @param ctx An execution context, from which the target executor is obtained. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(); @endcode + * + * @returns post(forward(function), ctx.get_executor(), + * forward(token)). + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void() @endcode + */ +template > +inline auto post(Function&& function, ExecutionContext& ctx, + NullaryToken&& token = default_completion_token_t< + typename ExecutionContext::executor_type>(), + constraint_t< + is_void()>>::value + > = 0, + constraint_t< + is_convertible::value + > = 0) + -> decltype( + async_initiate( + declval>(), + token, static_cast(function))) +{ + return async_initiate( + detail::initiate_post_with_executor< + typename ExecutionContext::executor_type>(ctx.get_executor()), + token, static_cast(function)); +} + +/// Submits a function to be run on a specified execution context, and passes +/// the result to a completion handler. +/** + * @param function A nullary function to be executed on the target executor. + * + * @param ctx An execution context, from which the target executor is obtained. + * + * @param token The @ref completion_token that will be used to produce a + * completion handler. The function signature of the completion handler must be: + * @code void handler(); @endcode + * + * @returns post(forward(function), ctx.get_executor(), + * forward(token)). + * + * @note If the function object throws an exception, that exception is allowed + * to propagate to the target executor. The behaviour in this case is dependent + * on the executor. For example, boost::asio::io_context will allow the + * exception to propagate to the caller that runs the @c io_context, whereas + * boost::asio::thread_pool will call @c std::terminate. + * + * @par Completion Signature + * @code void(decay_t()>>) @endcode + */ +template ()>>)) CompletionToken + = default_completion_token_t> +inline auto post(Function&& function, ExecutionContext& ctx, + CompletionToken&& token = default_completion_token_t< + typename ExecutionContext::executor_type>(), + constraint_t< + !is_void()>>::value + > = 0, + constraint_t< + is_convertible::value + > = 0) + -> decltype( + async_initiate)>( + declval>(), + token, static_cast(function))) +{ + return async_initiate)>( + detail::initiate_post_with_executor< + typename ExecutionContext::executor_type>(ctx.get_executor()), + token, static_cast(function)); } } // namespace asio diff --git a/test/defer.cpp b/test/defer.cpp index 282ca474..170ad3c2 100644 --- a/test/defer.cpp +++ b/test/defer.cpp @@ -16,10 +16,218 @@ // Test that header file is self-contained. #include +#include #include "unit_test.hpp" +using namespace boost::asio; +namespace bindns = std; +using bindns::placeholders::_1; +using bindns::placeholders::_2; + +class move_only_result +{ +public: + explicit move_only_result(int value) + : value_(value) + { + } + + move_only_result(move_only_result&& other) + : value_(other.value_) + { + } + + int value() const + { + return value_; + } + +private: + int value_; +}; + +static int function_count = 0; + +void void_function() +{ + ++function_count; +} + +struct void_function_object +{ + void_function_object() = default; + void_function_object(void_function_object&&) = default; + + void operator()() && + { + ++function_count; + } +}; + +void void_handler(int* count) +{ + ++(*count); +} + +move_only_result move_only_result_function() +{ + ++function_count; + return move_only_result(42); +} + +struct move_only_result_function_object +{ + move_only_result_function_object() = default; + move_only_result_function_object( + move_only_result_function_object&&) = default; + + move_only_result operator()() && + { + ++function_count; + return move_only_result(42); + } +}; + +void move_only_result_handler( + move_only_result result_in, int* count, int* result_out) +{ + ++(*count); + *result_out = result_in.value(); +} + +void defer_function_test() +{ + io_context ctx(1); + + function_count = 0; + int handler_count = 0; + defer(void_function, ctx.get_executor(), + bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + defer(void_function, ctx, bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + defer(void_function_object(), ctx.get_executor(), + bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + defer(void_function_object(), ctx, + bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + int handler_result = 0; + defer(move_only_result_function, ctx.get_executor(), + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); + + function_count = 0; + handler_count = 0; + handler_result = 0; + defer(move_only_result_function, ctx, + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); + + function_count = 0; + handler_count = 0; + handler_result = 0; + defer(move_only_result_function_object(), ctx.get_executor(), + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); + + function_count = 0; + handler_count = 0; + handler_result = 0; + defer(move_only_result_function_object(), ctx, + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); +} + BOOST_ASIO_TEST_SUITE ( "defer", - BOOST_ASIO_TEST_CASE(null_test) + BOOST_ASIO_TEST_CASE(defer_function_test) ) diff --git a/test/dispatch.cpp b/test/dispatch.cpp index 07730812..61336a13 100644 --- a/test/dispatch.cpp +++ b/test/dispatch.cpp @@ -16,10 +16,218 @@ // Test that header file is self-contained. #include +#include #include "unit_test.hpp" +using namespace boost::asio; +namespace bindns = std; +using bindns::placeholders::_1; +using bindns::placeholders::_2; + +class move_only_result +{ +public: + explicit move_only_result(int value) + : value_(value) + { + } + + move_only_result(move_only_result&& other) + : value_(other.value_) + { + } + + int value() const + { + return value_; + } + +private: + int value_; +}; + +static int function_count = 0; + +void void_function() +{ + ++function_count; +} + +struct void_function_object +{ + void_function_object() = default; + void_function_object(void_function_object&&) = default; + + void operator()() && + { + ++function_count; + } +}; + +void void_handler(int* count) +{ + ++(*count); +} + +move_only_result move_only_result_function() +{ + ++function_count; + return move_only_result(42); +} + +struct move_only_result_function_object +{ + move_only_result_function_object() = default; + move_only_result_function_object( + move_only_result_function_object&&) = default; + + move_only_result operator()() && + { + ++function_count; + return move_only_result(42); + } +}; + +void move_only_result_handler( + move_only_result result_in, int* count, int* result_out) +{ + ++(*count); + *result_out = result_in.value(); +} + +void dispatch_function_test() +{ + io_context ctx(1); + + function_count = 0; + int handler_count = 0; + dispatch(void_function, ctx.get_executor(), + bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + dispatch(void_function, ctx, bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + dispatch(void_function_object(), ctx.get_executor(), + bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + dispatch(void_function_object(), ctx, + bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + int handler_result = 0; + dispatch(move_only_result_function, ctx.get_executor(), + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); + + function_count = 0; + handler_count = 0; + handler_result = 0; + dispatch(move_only_result_function, ctx, + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); + + function_count = 0; + handler_count = 0; + handler_result = 0; + dispatch(move_only_result_function_object(), ctx.get_executor(), + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); + + function_count = 0; + handler_count = 0; + handler_result = 0; + dispatch(move_only_result_function_object(), ctx, + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); +} + BOOST_ASIO_TEST_SUITE ( "dispatch", - BOOST_ASIO_TEST_CASE(null_test) + BOOST_ASIO_TEST_CASE(dispatch_function_test) ) diff --git a/test/post.cpp b/test/post.cpp index 6a4e426e..ea86795a 100644 --- a/test/post.cpp +++ b/test/post.cpp @@ -16,10 +16,218 @@ // Test that header file is self-contained. #include +#include #include "unit_test.hpp" +using namespace boost::asio; +namespace bindns = std; +using bindns::placeholders::_1; +using bindns::placeholders::_2; + +class move_only_result +{ +public: + explicit move_only_result(int value) + : value_(value) + { + } + + move_only_result(move_only_result&& other) + : value_(other.value_) + { + } + + int value() const + { + return value_; + } + +private: + int value_; +}; + +static int function_count = 0; + +void void_function() +{ + ++function_count; +} + +struct void_function_object +{ + void_function_object() = default; + void_function_object(void_function_object&&) = default; + + void operator()() && + { + ++function_count; + } +}; + +void void_handler(int* count) +{ + ++(*count); +} + +move_only_result move_only_result_function() +{ + ++function_count; + return move_only_result(42); +} + +struct move_only_result_function_object +{ + move_only_result_function_object() = default; + move_only_result_function_object( + move_only_result_function_object&&) = default; + + move_only_result operator()() && + { + ++function_count; + return move_only_result(42); + } +}; + +void move_only_result_handler( + move_only_result result_in, int* count, int* result_out) +{ + ++(*count); + *result_out = result_in.value(); +} + +void post_function_test() +{ + io_context ctx(1); + + function_count = 0; + int handler_count = 0; + post(void_function, ctx.get_executor(), + bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + post(void_function, ctx, bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + post(void_function_object(), ctx.get_executor(), + bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + post(void_function_object(), ctx, + bindns::bind(void_handler, &handler_count)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + + function_count = 0; + handler_count = 0; + int handler_result = 0; + post(move_only_result_function, ctx.get_executor(), + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); + + function_count = 0; + handler_count = 0; + handler_result = 0; + post(move_only_result_function, ctx, + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); + + function_count = 0; + handler_count = 0; + handler_result = 0; + post(move_only_result_function_object(), ctx.get_executor(), + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); + + function_count = 0; + handler_count = 0; + handler_result = 0; + post(move_only_result_function_object(), ctx, + bindns::bind(move_only_result_handler, _1, + &handler_count, &handler_result)); + + BOOST_ASIO_CHECK(function_count == 0); + BOOST_ASIO_CHECK(handler_count == 0); + BOOST_ASIO_CHECK(handler_result == 0); + + ctx.restart(); + ctx.run(); + + BOOST_ASIO_CHECK(function_count == 1); + BOOST_ASIO_CHECK(handler_count == 1); + BOOST_ASIO_CHECK(handler_result == 42); +} + BOOST_ASIO_TEST_SUITE ( "post", - BOOST_ASIO_TEST_CASE(null_test) + BOOST_ASIO_TEST_CASE(post_function_test) )