From f49a0add53bea1c610934bf47e8a36fa95b71dec Mon Sep 17 00:00:00 2001 From: Christopher Kohlhoff Date: Tue, 4 Nov 2025 23:42:51 +1100 Subject: [PATCH] Add redirect_disposition completion token adapter. --- doc/overview/cpp20_coroutines.qbk | 7 +- doc/overview/token_adapters.qbk | 36 ++ doc/quickref.xml | 3 + include/boost/asio.hpp | 1 + .../boost/asio/impl/redirect_disposition.hpp | 455 ++++++++++++++++++ include/boost/asio/redirect_disposition.hpp | 117 +++++ test/Jamfile.v2 | 2 + test/redirect_disposition.cpp | 443 +++++++++++++++++ 8 files changed, 1062 insertions(+), 2 deletions(-) create mode 100644 include/boost/asio/impl/redirect_disposition.hpp create mode 100644 include/boost/asio/redirect_disposition.hpp create mode 100644 test/redirect_disposition.cpp diff --git a/doc/overview/cpp20_coroutines.qbk b/doc/overview/cpp20_coroutines.qbk index 129c0adc..1947f36b 100644 --- a/doc/overview/cpp20_coroutines.qbk +++ b/doc/overview/cpp20_coroutines.qbk @@ -88,8 +88,10 @@ passed back to the coroutine as a `system_error` exception. [heading Error Handling] To perform explicit error handling, rather than the default exception-throwing -behaviour, use the [link boost_asio.reference.as_tuple `as_tuple`] or -[link boost_asio.reference.redirect_error `redirect_error`] completion token adapters. +behaviour, use the [link boost_asio.reference.as_tuple `as_tuple`], +[link boost_asio.reference.redirect_disposition `redirect_disposition`] or +[link boost_asio.reference.redirect_error `redirect_error`] or completion token +adapters. The `as_tuple` completion token adapter packages the completion handler arguments into a single tuple, which is then returned as the result of @@ -296,6 +298,7 @@ protocol in terms of a coroutine: [link boost_asio.reference.co_spawn co_spawn], [link boost_asio.reference.detached detached], [link boost_asio.reference.as_tuple as_tuple], +[link boost_asio.reference.redirect_disposition redirect_disposition], [link boost_asio.reference.redirect_error redirect_error], [link boost_asio.reference.awaitable awaitable], [link boost_asio.reference.use_awaitable_t use_awaitable_t], diff --git a/doc/overview/token_adapters.qbk b/doc/overview/token_adapters.qbk index b2f6987f..55072f15 100644 --- a/doc/overview/token_adapters.qbk +++ b/doc/overview/token_adapters.qbk @@ -94,6 +94,42 @@ expression will no longer throw an exception on resumption. // ... } +[heading redirect_disposition] + +The [link boost_asio.reference.redirect_disposition `redirect_disposition`] function +adapts a completion token to capture any [link boost_asio.reference.Disposition +disposition] produced by an operation into a specified variable. In doing so, it +modifies the completion signature to remove the initial disposition parameter. + +This example shows the `redirect_disposition` adapter applied to a lambda, to +specify that the disposition should be captured into `my_exception`. The +`error_code` (which is a disposition type) is no longer passed to the completion +handler, but the remaining arguments are passed through as-is. + + std::exception_ptr my_exception; // N.B. must be valid until operation completes + // ... + my_socket.async_read_some(my_buffer, + boost::asio::redirect_disposition( + [](std::size_t bytes_transferred) + { + // ... + }, my_exception)); + +When applied to completion tokens that cause the initiating function to produce +a result, such as [link boost_asio.reference.use_awaitable `use_awaitable`], the +result is returned unmodified. However, if the operation fails, the `co_await` +expression will no longer throw an exception on resumption. + + boost::asio::awaitable my_coroutine() + { + // ... + std::exception my_exception; + std::size_t bytes_transferred = + co_await my_socket.async_read_some(my_buffer, + boost::asio::redirect_disposition(boost::asio::use_awaitable, my_exception)); + // ... + } + [heading as_tuple] The [link boost_asio.reference.as_tuple `as_tuple`] adapter can be used to specify diff --git a/doc/quickref.xml b/doc/quickref.xml index b467a057..be8f5e50 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -234,8 +234,10 @@ partial_cancellation_slot_binder partial_executor_binder partial_immediate_executor_binder + partial_redirect_disposition prepend_t recycling_allocator + redirect_disposition_t redirect_error_t strand thread_pool::basic_executor_type @@ -277,6 +279,7 @@ make_work_guard post prepend + redirect_disposition redirect_error spawn this_coro::reset_cancellation_state diff --git a/include/boost/asio.hpp b/include/boost/asio.hpp index f1dc27e3..6be59bb6 100644 --- a/include/boost/asio.hpp +++ b/include/boost/asio.hpp @@ -163,6 +163,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/asio/impl/redirect_disposition.hpp b/include/boost/asio/impl/redirect_disposition.hpp new file mode 100644 index 00000000..c37d8535 --- /dev/null +++ b/include/boost/asio/impl/redirect_disposition.hpp @@ -0,0 +1,455 @@ + +// impl/redirect_disposition.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_ASIO_IMPL_REDIRECT_DISPOSITION_HPP +#define BOOST_ASIO_IMPL_REDIRECT_DISPOSITION_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace boost { +namespace asio { +namespace detail { + +// Class to adapt a redirect_disposition_t as a completion handler. +template +class redirect_disposition_handler +{ +public: + typedef void result_type; + + template + redirect_disposition_handler( + redirect_disposition_t e) + : d_(e.d_), + handler_(static_cast(e.token_)) + { + } + + template + redirect_disposition_handler(Disposition& d, RedirectedHandler&& h) + : d_(d), + handler_(static_cast(h)) + { + } + + void operator()() + { + static_cast(handler_)(); + } + + template + enable_if_t< + !is_same, Disposition>::value + > + operator()(Arg&& arg, Args&&... args) + { + static_cast(handler_)( + static_cast(arg), + static_cast(args)...); + } + + template + void operator()(const Disposition& d, Args&&... args) + { + d_ = d; + static_cast(handler_)(static_cast(args)...); + } + +//private: + Disposition& d_; + Handler handler_; +}; + +template +class redirect_disposition_handler +{ +public: + typedef void result_type; + + template + redirect_disposition_handler( + redirect_disposition_t e) + : d_(e.d_), + handler_(static_cast(e.token_)) + { + } + + template + redirect_disposition_handler(std::exception_ptr& d, RedirectedHandler&& h) + : d_(d), + handler_(static_cast(h)) + { + } + + void operator()() + { + static_cast(handler_)(); + } + + template + enable_if_t< + !is_disposition>::value + > + operator()(Arg&& arg, Args&&... args) + { + static_cast(handler_)( + static_cast(arg), + static_cast(args)...); + } + + template + enable_if_t< + is_disposition::value + > + operator()(const Disposition& d, Args&&... args) + { + d_ = disposition_traits::to_exception_ptr(d); + static_cast(handler_)(static_cast(args)...); + } + +//private: + std::exception_ptr& d_; + Handler handler_; +}; + +template +inline bool asio_handler_is_continuation( + redirect_disposition_handler* this_handler) +{ + return boost_asio_handler_cont_helpers::is_continuation( + this_handler->handler_); +} + +template +struct redirect_disposition_signature +{ + typedef Signature type; +}; + +template +struct redirect_disposition_signature< + Disposition, R(Disposition, Args...)> +{ + typedef R type(Args...); +}; + +template +struct redirect_disposition_signature< + Disposition, R(const Disposition&, Args...)> +{ + typedef R type(Args...); +}; + +template +struct redirect_disposition_signature< + Disposition, R(Disposition, Args...) &> +{ + typedef R type(Args...) &; +}; + +template +struct redirect_disposition_signature< + Disposition, R(const Disposition&, Args...) &> +{ + typedef R type(Args...) &; +}; + +template +struct redirect_disposition_signature< + Disposition, R(Disposition, Args...) &&> +{ + typedef R type(Args...) &&; +}; + +template +struct redirect_disposition_signature< + Disposition, R(const Disposition&, Args...) &&> +{ + typedef R type(Args...) &&; +}; + +#if defined(BOOST_ASIO_HAS_NOEXCEPT_FUNCTION_TYPE) + +template +struct redirect_disposition_signature< + Disposition, R(Disposition, Args...) noexcept> +{ + typedef R type(Args...) & noexcept; +}; + +template +struct redirect_disposition_signature< + Disposition, R(const Disposition&, Args...) noexcept> +{ + typedef R type(Args...) & noexcept; +}; + +template +struct redirect_disposition_signature< + Disposition, R(Disposition, Args...) & noexcept> +{ + typedef R type(Args...) & noexcept; +}; + +template +struct redirect_disposition_signature< + Disposition, R(const Disposition&, Args...) & noexcept> +{ + typedef R type(Args...) & noexcept; +}; + +template +struct redirect_disposition_signature< + Disposition, R(Disposition, Args...) && noexcept> +{ + typedef R type(Args...) && noexcept; +}; + +template +struct redirect_disposition_signature< + Disposition, R(const Disposition&, Args...) && noexcept> +{ + typedef R type(Args...) && noexcept; +}; + +#endif // defined(BOOST_ASIO_HAS_NOEXCEPT_FUNCTION_TYPE) + +template +struct redirect_disposition_signature< + std::exception_ptr, R(Disposition, Args...), + enable_if_t::value>> +{ + typedef R type(Args...); +}; + +template +struct redirect_disposition_signature< + std::exception_ptr, R(const Disposition&, Args...), + enable_if_t::value>> +{ + typedef R type(Args...); +}; + +template +struct redirect_disposition_signature< + std::exception_ptr, R(Disposition, Args...) &, + enable_if_t::value>> +{ + typedef R type(Args...) &; +}; + +template +struct redirect_disposition_signature< + std::exception_ptr, R(const Disposition&, Args...) &, + enable_if_t::value>> +{ + typedef R type(Args...) &; +}; + +template +struct redirect_disposition_signature< + std::exception_ptr, R(Disposition, Args...) &&, + enable_if_t::value>> +{ + typedef R type(Args...) &&; +}; + +template +struct redirect_disposition_signature< + std::exception_ptr, R(const Disposition&, Args...) &&, + enable_if_t::value>> +{ + typedef R type(Args...) &&; +}; + +#if defined(BOOST_ASIO_HAS_NOEXCEPT_FUNCTION_TYPE) + +template +struct redirect_disposition_signature< + std::exception_ptr, R(Disposition, Args...) noexcept, + enable_if_t::value>> +{ + typedef R type(Args...) & noexcept; +}; + +template +struct redirect_disposition_signature< + std::exception_ptr, R(const Disposition&, Args...) noexcept, + enable_if_t::value>> +{ + typedef R type(Args...) & noexcept; +}; + +template +struct redirect_disposition_signature< + std::exception_ptr, R(Disposition, Args...) & noexcept, + enable_if_t::value>> +{ + typedef R type(Args...) & noexcept; +}; + +template +struct redirect_disposition_signature< + std::exception_ptr, R(const Disposition&, Args...) & noexcept, + enable_if_t::value>> +{ + typedef R type(Args...) & noexcept; +}; + +template +struct redirect_disposition_signature< + std::exception_ptr, R(Disposition, Args...) && noexcept, + enable_if_t::value>> +{ + typedef R type(Args...) && noexcept; +}; + +template +struct redirect_disposition_signature< + std::exception_ptr, R(const Disposition&, Args...) && noexcept, + enable_if_t::value>> +{ + typedef R type(Args...) && noexcept; +}; + +#endif // defined(BOOST_ASIO_HAS_NOEXCEPT_FUNCTION_TYPE) + +} // namespace detail + +#if !defined(GENERATING_DOCUMENTATION) + +template +struct async_result< + redirect_disposition_t, Signature> + : async_result::type> +{ + template + struct init_wrapper : detail::initiation_base + { + using detail::initiation_base::initiation_base; + + template + void operator()(Handler&& handler, + Disposition* d, Args&&... args) && + { + static_cast(*this)( + detail::redirect_disposition_handler>( + *d, static_cast(handler)), + static_cast(args)...); + } + + template + void operator()(Handler&& handler, + Disposition* d, Args&&... args) const & + { + static_cast(*this)( + detail::redirect_disposition_handler>( + *d, static_cast(handler)), + static_cast(args)...); + } + }; + + template + static auto initiate(Initiation&& initiation, + RawCompletionToken&& token, Args&&... args) + -> decltype( + async_initiate< + conditional_t< + is_const>::value, + const CompletionToken, CompletionToken>, + typename detail::redirect_disposition_signature< + Disposition, Signature>::type>( + declval>>(), + token.token_, &token.d_, static_cast(args)...)) + { + return async_initiate< + conditional_t< + is_const>::value, + const CompletionToken, CompletionToken>, + typename detail::redirect_disposition_signature< + Disposition, Signature>::type>( + init_wrapper>( + static_cast(initiation)), + token.token_, &token.d_, static_cast(args)...); + } +}; + +template