chriskohlhoff / asio

Asio C++ Library
http://think-async.com/Asio
4.72k stars 1.19k forks source link

Ambiguous call to `async_connect` because of unconstrained `ConnectCondition` #1476

Closed ashtum closed 5 days ago

ashtum commented 1 month ago

When using an executor with a default completion token type, calls to asio::async_connect become ambiguous because the unconstrained ConnectCondition parameter matches the passed completion token.

I believe constraining the ConnectCondition can fix the issue.

https://godbolt.org/z/aqxns58n5

#include <boost/asio.hpp>

using namespace boost;

asio::awaitable<void> co_main()
{
  auto exec     = co_await asio::this_coro::executor;
  auto resolver = asio::deferred.as_default_on(asio::ip::tcp::resolver(exec));
  auto socket   = asio::deferred.as_default_on(asio::ip::tcp::socket(exec));

  auto results  = co_await resolver.async_resolve("example.com", "80");
  auto [ec, ep] = co_await asio::async_connect(socket, results, asio::as_tuple(asio::deferred));
}

int main()
{
  asio::io_context ioc;

  asio::co_spawn(ioc, co_main(), asio::detached);

  ioc.run();
}
<source>:12:28: error: call to 'async_connect' is ambiguous
   12 |   auto [ec, ep] = co_await asio::async_connect(socket, results, asio::as_tuple(asio::deferred));
      |                            ^~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/libs/boost_1_84_0/boost/asio/impl/connect.hpp:694:13: note: candidate function [with Protocol = boost::asio::ip::tcp, Executor = boost::asio::deferred_t::executor_with_default<boost::asio::any_io_executor>, EndpointSequence = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>, RangeConnectToken = boost::asio::as_tuple_t<boost::asio::deferred_t>]
  694 | inline auto async_connect(basic_socket<Protocol, Executor>& s,
      |             ^
/opt/compiler-explorer/libs/boost_1_84_0/boost/asio/impl/connect.hpp:750:13: note: candidate function [with Protocol = boost::asio::ip::tcp, Executor = boost::asio::deferred_t::executor_with_default<boost::asio::any_io_executor>, EndpointSequence = boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>, ConnectCondition = boost::asio::as_tuple_t<boost::asio::deferred_t>, RangeConnectToken = boost::asio::deferred_t]
  750 | inline auto async_connect(basic_socket<Protocol, Executor>& s,
      |             ^
1 error generated.
Compiler returned: 1
sehe commented 1 month ago

There are more cases where default completion token cause ambiguous overloads [source:https://stackoverflow.com/a/78499786/85371]

This minimal example shows two side-by-side (one of which is the async_connect from the first issue comment)

Live On Compiler Explorer

#include <boost/asio.hpp>
#include <boost/core/ignore_unused.hpp>
namespace asio = boost::asio;
using asio::ip::tcp;

int main() {
    auto x = asio::system_executor{};
    auto r = asio::deferred.as_default_on(tcp::resolver{x});
    auto s = asio::deferred.as_default_on(tcp::socket{x});

    asio::streambuf b;
    using Token = asio::default_completion_token_t<decltype(s)::executor_type>;

    // auto op1 = asio::async_connect(s, r.resolve("", "8989"), Token());// BROKEN
    auto op1 = asio::async_connect(s, r.resolve("", "8989")); // Workaround

    // but conversely:

    // auto op2 = asio::async_read(s, b, asio::transfer_all()); // BROKEN
    auto op2 = asio::async_read(s, b, asio::transfer_all(), Token()); // Workaround

    boost::ignore_unused(op1, op2);
}
ashtum commented 5 days ago

Addressed in https://github.com/chriskohlhoff/asio/commit/a3450a4898ed9006c2d56dd9151a2ef368aaea73.

sehe commented 5 days ago

Technically, the additional cases were fixed in some subsequent commits (ec5eee84cfd647 to compile my examples), I can confirm the fix is Boost Asio's master branch meaning it will be in the next Boost release :tada: