| 41 | template <typename T, |
| 42 | boost::asio::completion_token_for<void(boost::system::error_code)> CompletionToken> |
| 43 | auto async_write_messages(tcp::socket& socket, |
| 44 | const T& message, std::size_t repeat_count, |
| 45 | CompletionToken&& token) |
| 46 | // The return type of the initiating function is deduced from the combination |
| 47 | // of: |
| 48 | // |
| 49 | // - the CompletionToken type, |
| 50 | // - the completion handler signature, and |
| 51 | // - the asynchronous operation's initiation function object. |
| 52 | // |
| 53 | // When the completion token is a simple callback, the return type is always |
| 54 | // void. In this example, when the completion token is boost::asio::yield_context |
| 55 | // (used for stackful coroutines) the return type would also be void, as |
| 56 | // there is no non-error argument to the completion handler. When the |
| 57 | // completion token is boost::asio::use_future it would be std::future<void>. When |
| 58 | // the completion token is boost::asio::deferred, the return type differs for each |
| 59 | // asynchronous operation. |
| 60 | // |
| 61 | // In C++20 we can omit the return type as it is automatically deduced from |
| 62 | // the return type of boost::asio::async_compose. |
| 63 | { |
| 64 | // Encode the message and copy it into an allocated buffer. The buffer will |
| 65 | // be maintained for the lifetime of the composed asynchronous operation. |
| 66 | std::ostringstream os; |
| 67 | os << message; |
| 68 | std::unique_ptr<std::string> encoded_message(new std::string(os.str())); |
| 69 | |
| 70 | // Create a steady_timer to be used for the delay between messages. |
| 71 | std::unique_ptr<boost::asio::steady_timer> delay_timer( |
| 72 | new boost::asio::steady_timer(socket.get_executor())); |
| 73 | |
| 74 | // To manage the cycle between the multiple underlying asynchronous |
| 75 | // operations, our implementation is a state machine. |
| 76 | enum { starting, waiting, writing }; |
| 77 | |
| 78 | // The boost::asio::async_compose function takes: |
| 79 | // |
| 80 | // - our asynchronous operation implementation, |
| 81 | // - the completion token, |
| 82 | // - the completion handler signature, and |
| 83 | // - any I/O objects (or executors) used by the operation |
| 84 | // |
| 85 | // It then wraps our implementation, which is implemented here as a state |
| 86 | // machine in a lambda, in an intermediate completion handler that meets the |
| 87 | // requirements of a conforming asynchronous operation. This includes |
| 88 | // tracking outstanding work against the I/O executors associated with the |
| 89 | // operation (in this example, this is the socket's executor). |
| 90 | // |
| 91 | // The first argument to our lambda is a reference to the enclosing |
| 92 | // intermediate completion handler. This intermediate completion handler is |
| 93 | // provided for us by the boost::asio::async_compose function, and takes care |
| 94 | // of all the details required to implement a conforming asynchronous |
| 95 | // operation. When calling an underlying asynchronous operation, we pass it |
| 96 | // this enclosing intermediate completion handler as the completion token. |
| 97 | // |
| 98 | // All arguments to our lambda after the first must be defaulted to allow the |
| 99 | // state machine to be started, as well as to allow the completion handler to |
| 100 | // match the completion signature of both the async_write and |