| 37 | template <typename T, |
| 38 | boost::asio::completion_token_for<void(boost::system::error_code)> CompletionToken> |
| 39 | auto async_write_message(tcp::socket& socket, |
| 40 | const T& message, CompletionToken&& token) |
| 41 | // The return type of the initiating function is deduced from the combination |
| 42 | // of: |
| 43 | // |
| 44 | // - the CompletionToken type, |
| 45 | // - the completion handler signature, and |
| 46 | // - the asynchronous operation's initiation function object. |
| 47 | // |
| 48 | // When the completion token is a simple callback, the return type is always |
| 49 | // void. In this example, when the completion token is boost::asio::yield_context |
| 50 | // (used for stackful coroutines) the return type would also be void, as |
| 51 | // there is no non-error argument to the completion handler. When the |
| 52 | // completion token is boost::asio::use_future it would be std::future<void>. When |
| 53 | // the completion token is boost::asio::deferred, the return type differs for each |
| 54 | // asynchronous operation. |
| 55 | // |
| 56 | // In C++20 we can omit the return type as it is automatically deduced from |
| 57 | // the return type of boost::asio::async_initiate. |
| 58 | { |
| 59 | // In addition to determining the mechanism by which an asynchronous |
| 60 | // operation delivers its result, a completion token also determines the time |
| 61 | // when the operation commences. For example, when the completion token is a |
| 62 | // simple callback the operation commences before the initiating function |
| 63 | // returns. However, if the completion token's delivery mechanism uses a |
| 64 | // future, we might instead want to defer initiation of the operation until |
| 65 | // the returned future object is waited upon. |
| 66 | // |
| 67 | // To enable this, when implementing an asynchronous operation we must |
| 68 | // package the initiation step as a function object. The initiation function |
| 69 | // object's call operator is passed the concrete completion handler produced |
| 70 | // by the completion token. This completion handler matches the asynchronous |
| 71 | // operation's completion handler signature, which in this example is: |
| 72 | // |
| 73 | // void(boost::system::error_code error) |
| 74 | // |
| 75 | // The initiation function object also receives any additional arguments |
| 76 | // required to start the operation. (Note: We could have instead passed these |
| 77 | // arguments in the lambda capture set. However, we should prefer to |
| 78 | // propagate them as function call arguments as this allows the completion |
| 79 | // token to optimise how they are passed. For example, a lazy future which |
| 80 | // defers initiation would need to make a decay-copy of the arguments, but |
| 81 | // when using a simple callback the arguments can be trivially forwarded |
| 82 | // straight through.) |
| 83 | auto initiation = []( |
| 84 | boost::asio::completion_handler_for<void(boost::system::error_code)> |
| 85 | auto&& completion_handler, |
| 86 | tcp::socket& socket, |
| 87 | std::unique_ptr<std::string> encoded_message) |
| 88 | { |
| 89 | // In this example, the composed operation's intermediate completion |
| 90 | // handler is implemented as a hand-crafted function object, rather than |
| 91 | // using a lambda or std::bind. |
| 92 | struct intermediate_completion_handler |
| 93 | { |
| 94 | // The intermediate completion handler holds a reference to the socket so |
| 95 | // that it can obtain the I/O executor (see get_executor below). |
| 96 | tcp::socket& socket_; |