chriskohlhoff / asio

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

fix: bind_* associating functions must respect value-category of wrapped object #1540

Open justend29 opened 1 month ago

justend29 commented 1 month ago

Issue

The helper functions for binding associations to completion handlers, such as bind_executor, did not previously respect the value-category of wrapper. Consequently, when using move-only values with certain completion-handlers, build failures result since although the wrapper was an r-value, the wrapped target was not treated as such.

Changes

The solution is to forward the value-category of the wrapper to the wrapped target. This is already done in other wrappers in asio, like append. For these bind wrappers, an overload of their forwarding call operator is added to each wrapper. Moving the target is in C++03 form, using static_cast over std::move, to remain consistent with the rest of asio.

Failing Example

Although contrived, this example fails to build because the move-only completion handler Intermediary is not handled properly by bind_allocator. I've hit this build failure in multiple more realistic scenarios, particularly when using asio::awaitable, where the result of the awaitable frame cannot be allocated here due to the improper value-category.

#include <iostream>

#include <boost/asio.hpp>
#include <boost/system.hpp>

namespace asio = boost::asio;
namespace sys = boost::system;

using Signature = void(sys::error_code);

struct Intermediary {
  Intermediary(asio::any_completion_handler<Signature> completion)
      : _completion{std::move(completion)}
  {}

  Intermediary(const Intermediary&) = delete;
  Intermediary(Intermediary&&) = default;

  asio::any_completion_handler<Signature> _completion;

  void operator()() && { std::move(_completion)(sys::error_code{}); }
};

template <asio::completion_token_for<Signature> Token>
auto get_move_only(Token&& token)
{
  auto initiate = []<typename C>(C&& completion) {
    // Fails:
    asio::dispatch(
        asio::bind_allocator(std::allocator<void>{}, Intermediary{std::forward<C>(completion)}));

    // Success:
    // asio::dispatch(Intermediary{std::forward<C>(completion)});
  };

  return asio::async_initiate<Token, Signature>(initiate, token);
}

int main()
{
  asio::io_context ioc{};

  get_move_only([](const sys::error_code err){
    std::cout << err.message() << std::endl;
  });

  ioc.run();
}