From 5953eb3405a50a2f41eb12f2d0832ee9f8edad2d Mon Sep 17 00:00:00 2001 From: Robert Leahy Date: Thu, 27 Nov 2025 12:59:30 -0500 Subject: [PATCH] asioexec::completion_token & ::use_sender: Constrain Initiate Previously async_result:: and async_result::initiate: - Used automatic return type deduction, and - Were not constrained on any properties of the arguments thereto This meant that initiating functions of the form were problematic: template > inline auto async_read(AsyncReadStream& s, basic_streambuf& b, ReadToken&& token = default_completion_token_t< typename AsyncReadStream::executor_type>(), constraint_t< !is_completion_condition::value > = 0) -> decltype( async_initiate( declval>(), token, basic_streambuf_ref(b), transfer_all())); Note that: - The above is an actual initiating function from Asio, and - The return type is defined in terms of the type returned by async_initiate The latter of the two points above means that the compiler must determine the type of an expression involving async_initiate. Since that expression transitively defers to async_result<...>::initiate this meant that the compiler had to determine the return type thereof. Since (as mentioned above) that function uses automatic return type deduction that meant the compiler had to substitute into the body thereof. Since errors in such a context are not SFINAE-friendly this meant that if the variadic pack of arguments thereto did not consist only of decay- copyable types there would be a hard compilation error (rather than the overload simply being removed from the candidate set). Both newly-added unit tests failed to compile before this change due to the above. Added a constraint to both previously-mentioned initiate functions to address the above. --- include/asioexec/completion_token.hpp | 1 + include/asioexec/use_sender.hpp | 1 + test/asioexec/test_completion_token.cpp | 21 +++++++++++++++++++++ test/asioexec/test_use_sender.cpp | 16 ++++++++++++++++ 4 files changed, 39 insertions(+) diff --git a/include/asioexec/completion_token.hpp b/include/asioexec/completion_token.hpp index fe1cc2155..d5c63f94e 100644 --- a/include/asioexec/completion_token.hpp +++ b/include/asioexec/completion_token.hpp @@ -527,6 +527,7 @@ namespace ASIOEXEC_ASIO_NAMESPACE { template struct async_result<::asioexec::completion_token_t, Signatures...> { template + requires (std::is_constructible_v, Args> && ...) static constexpr auto initiate(Initiation&& i, const ::asioexec::completion_token_t&, Args&&... args) { return ::asioexec::detail::completion_token::sender< diff --git a/include/asioexec/use_sender.hpp b/include/asioexec/use_sender.hpp index 0ae7c148b..d0e67460e 100644 --- a/include/asioexec/use_sender.hpp +++ b/include/asioexec/use_sender.hpp @@ -212,6 +212,7 @@ namespace ASIOEXEC_ASIO_NAMESPACE { template struct async_result<::asioexec::use_sender_t, Signatures...> { template + requires(std::is_constructible_v, Args> && ...) static constexpr auto initiate(Initiation&& i, const ::asioexec::use_sender_t&, Args&&... args) { return ::asioexec::detail::use_sender::sender( diff --git a/test/asioexec/test_completion_token.cpp b/test/asioexec/test_completion_token.cpp index dd478e59b..fc6f0c56b 100644 --- a/test/asioexec/test_completion_token.cpp +++ b/test/asioexec/test_completion_token.cpp @@ -997,4 +997,25 @@ namespace { CHECK(ctx.stopped()); } + TEST_CASE( + "Substitution into async_result::initiate is SFINAE-friendly", + "[asioexec][completion_token]") { + asio_impl::io_context ctx; + asio_impl::ip::tcp::socket socket(ctx); + asio_impl::streambuf buf; + // With a SFINAE-unfriendly async_result<...>::initiate the below line doesn't compile because there's a hard compilation error trying to consider the async_read overload for dynamic buffers + // + // See: https://github.com/NVIDIA/stdexec/issues/1684 + auto sender = asio_impl::async_read(socket, buf, completion_token); + auto op = ::stdexec::connect( + std::move(sender) | ::stdexec::then([](const auto ec, const auto bytes_transferred) { + CHECK(ec); + CHECK(!bytes_transferred); + }), + expect_void_receiver{}); + ::stdexec::start(op); + CHECK(ctx.run() != 0); + CHECK(ctx.stopped()); + } + } // namespace diff --git a/test/asioexec/test_use_sender.cpp b/test/asioexec/test_use_sender.cpp index 08eb472be..69b9963a5 100644 --- a/test/asioexec/test_use_sender.cpp +++ b/test/asioexec/test_use_sender.cpp @@ -250,4 +250,20 @@ namespace { CHECK(ctx.stopped()); } + TEST_CASE( + "Substitution into async_result::initiate is SFINAE-friendly", + "[asioexec][completion_token]") { + asio_impl::io_context ctx; + asio_impl::ip::tcp::socket socket(ctx); + asio_impl::streambuf buf; + // With a SFINAE-unfriendly async_result<...>::initiate the below line doesn't compile because there's a hard compilation error trying to consider the async_read overload for dynamic buffers + // + // See: https://github.com/NVIDIA/stdexec/issues/1684 + auto sender = asio_impl::async_read(socket, buf, use_sender); + auto op = ::stdexec::connect(std::move(sender), expect_error_receiver{}); + ::stdexec::start(op); + CHECK(ctx.run() != 0); + CHECK(ctx.stopped()); + } + } // namespace