From b8a2589e4a64d574df2d999e54768707f667ec0a Mon Sep 17 00:00:00 2001 From: Robert Leahy Date: Mon, 29 Dec 2025 10:38:29 -0500 Subject: [PATCH] asioexec::completion_token: Conversion From Mutable Lvalue to Rvalue Asio operations frequently declare completion signatures which are correct only if you ignore cv- and ref-qualification. That is to say an operation might declare void(int) as its completion signature but in actual fact complete with int& or const int& (rather than the expected int&&). asioexec::completion_token has tried to deal with the above since it was initially added (35a3e31590e736fbb7dd55324b3a7f991a059ce3), however this has not been without issue (8bb5b46f33a1d3058a182056edd6ddf5783c944d). The correct way to deal with the above issue (as reified by 8bb5b46f33a1d3058a182056edd6ddf5783c944d) is to use a combination of: - std::is_convertible_v, and - std::reference_converts_from_temporary_v To determine whether values should be forwarded through to the receiver or first converted. Unfortunately compilers/standard libraries that don't feature std::reference_converts_from_temporary_v are supported hence this fallback from 8bb5b46f33a1d3058a182056edd6ddf5783c944d: ( // Just using is_base_of_v is insufficient because it always reports false for built-in types (std::is_base_of_v, std::remove_cvref_t> || std::is_same_v, std::remove_cvref_t>) && // The returned type must be at least as cv-qualified as the input type (it can be more cv-qualified) at_least_as_qualified_v, std::remove_reference_t> && ( // Reference type must agree except... (std::is_lvalue_reference_v == std::is_lvalue_reference_v) || // ...special rules for const& which allows rvalues to bind thereto (std::is_lvalue_reference_v && std::is_const_v>) )) Unfortunately there's a typo under the comment which reads "[r]eference type must agree except:" Both sides of the equality comparison are std:: is_lvalue_reference_v thereby rendering it tautological. This had the effect of activating the overload constrained thereby in the case where: - The destination type (T) is an rvalue reference, and - The source type (U) is a mutable lvalue reference Leading to a compiler error of the following form: error: rvalue reference to type 'boost::system::error_code' cannot bind to lvalue of type 'boost::system::error_code' 100 | return static_cast(u); | ^~~~~~~~~~~~~~~~~~~ Which is #1724 (which this commit addresses). Added a reproducing test and fixed. --- include/asioexec/completion_token.hpp | 2 +- test/asioexec/test_completion_token.cpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/include/asioexec/completion_token.hpp b/include/asioexec/completion_token.hpp index d5c63f94e..e7a8b07af 100644 --- a/include/asioexec/completion_token.hpp +++ b/include/asioexec/completion_token.hpp @@ -92,7 +92,7 @@ namespace asioexec { at_least_as_qualified_v, std::remove_reference_t> && ( // Reference type must agree except... - (std::is_lvalue_reference_v == std::is_lvalue_reference_v) || + (std::is_lvalue_reference_v == std::is_lvalue_reference_v) || // ...special rules for const& which allows rvalues to bind thereto (std::is_lvalue_reference_v && std::is_const_v>) )) #endif diff --git a/test/asioexec/test_completion_token.cpp b/test/asioexec/test_completion_token.cpp index fc6f0c56b..43a72f05d 100644 --- a/test/asioexec/test_completion_token.cpp +++ b/test/asioexec/test_completion_token.cpp @@ -1018,4 +1018,19 @@ namespace { CHECK(ctx.stopped()); } + TEST_CASE( + "Asio operations which declare completion by value but send a mutable lvalue work", + "[asioexec][completion_token]") { + const auto initiating_function = [](auto&& token) { + return asio_impl::async_initiate( + [](auto&& h) { + int i = 5; + std::forward(h)(i); + }, + token); + }; + auto op = ::stdexec::connect(initiating_function(completion_token), expect_value_receiver{5}); + ::stdexec::start(op); + } + } // namespace