| 1 | // |
| 2 | // callback_wrapper.cpp |
| 3 | // ~~~~~~~~~~~~~~~~~~~~ |
| 4 | // |
| 5 | // Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
| 6 | // |
| 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying |
| 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) |
| 9 | // |
| 10 | |
| 11 | #include <boost/asio.hpp> |
| 12 | #include <iostream> |
| 13 | |
| 14 | //------------------------------------------------------------------------------ |
| 15 | |
| 16 | // This is a mock implementation of an API that uses a move-only function object |
| 17 | // for exposing a callback. The callback has the signature void(std::string). |
| 18 | |
| 19 | template <typename Callback> |
| 20 | void read_input(const std::string& prompt, Callback cb) |
| 21 | { |
| 22 | std::thread( |
| 23 | [prompt, cb = std::move(cb)]() mutable |
| 24 | { |
| 25 | std::cout << prompt << ": " ; |
| 26 | std::cout.flush(); |
| 27 | std::string line; |
| 28 | std::getline(is&: std::cin, str&: line); |
| 29 | std::move(cb)(std::move(line)); |
| 30 | }).detach(); |
| 31 | } |
| 32 | |
| 33 | //------------------------------------------------------------------------------ |
| 34 | |
| 35 | // This is an asynchronous operation that wraps the callback-based API. |
| 36 | |
| 37 | // The initiating function for the asynchronous operation. |
| 38 | template <typename CompletionToken> |
| 39 | auto async_read_input(const std::string& prompt, CompletionToken&& token) |
| 40 | { |
| 41 | // Define a function object that contains the code to launch the asynchronous |
| 42 | // operation. This is passed the concrete completion handler, followed by any |
| 43 | // additional arguments that were passed through the call to async_initiate. |
| 44 | auto init = [](auto handler, const std::string& prompt) |
| 45 | { |
| 46 | // According to the rules for asynchronous operations, we need to track |
| 47 | // outstanding work against the handler's associated executor until the |
| 48 | // asynchronous operation is complete. |
| 49 | auto work = boost::asio::make_work_guard(handler); |
| 50 | |
| 51 | // Launch the operation with a callback that will receive the result and |
| 52 | // pass it through to the asynchronous operation's completion handler. |
| 53 | read_input(prompt, |
| 54 | [ |
| 55 | handler = std::move(handler), |
| 56 | work = std::move(work) |
| 57 | ](std::string result) mutable |
| 58 | { |
| 59 | // Get the handler's associated allocator. If the handler does not |
| 60 | // specify an allocator, use the recycling allocator as the default. |
| 61 | auto alloc = boost::asio::get_associated_allocator( |
| 62 | handler, boost::asio::recycling_allocator<void>()); |
| 63 | |
| 64 | // Dispatch the completion handler through the handler's associated |
| 65 | // executor, using the handler's associated allocator. |
| 66 | boost::asio::dispatch(work.get_executor(), |
| 67 | boost::asio::bind_allocator(alloc, |
| 68 | [ |
| 69 | handler = std::move(handler), |
| 70 | result = std::string(result) |
| 71 | ]() mutable |
| 72 | { |
| 73 | std::move(handler)(result); |
| 74 | })); |
| 75 | }); |
| 76 | }; |
| 77 | |
| 78 | // The async_initiate function is used to transform the supplied completion |
| 79 | // token to the completion handler. When calling this function we explicitly |
| 80 | // specify the completion signature of the operation. We must also return the |
| 81 | // result of the call since the completion token may produce a return value, |
| 82 | // such as a future. |
| 83 | return boost::asio::async_initiate<CompletionToken, void(std::string)>( |
| 84 | init, // First, pass the function object that launches the operation, |
| 85 | token, // then the completion token that will be transformed to a handler, |
| 86 | prompt); // and, finally, any additional arguments to the function object. |
| 87 | } |
| 88 | |
| 89 | //------------------------------------------------------------------------------ |
| 90 | |
| 91 | void test_callback() |
| 92 | { |
| 93 | boost::asio::io_context io_context; |
| 94 | |
| 95 | // Test our asynchronous operation using a lambda as a callback. We will use |
| 96 | // an io_context to specify an associated executor. |
| 97 | async_read_input(prompt: "Enter your name" , |
| 98 | token: boost::asio::bind_executor(ctx&: io_context, |
| 99 | t: [](const std::string& result) |
| 100 | { |
| 101 | std::cout << "Hello " << result << "\n" ; |
| 102 | })); |
| 103 | |
| 104 | io_context.run(); |
| 105 | } |
| 106 | |
| 107 | //------------------------------------------------------------------------------ |
| 108 | |
| 109 | void test_deferred() |
| 110 | { |
| 111 | boost::asio::io_context io_context; |
| 112 | |
| 113 | // Test our asynchronous operation using the deferred completion token. This |
| 114 | // token causes the operation's initiating function to package up the |
| 115 | // operation with its arguments to return a function object, which may then be |
| 116 | // used to launch the asynchronous operation. |
| 117 | auto op = async_read_input(prompt: "Enter your name" , token: boost::asio::deferred); |
| 118 | |
| 119 | // Launch our asynchronous operation using a lambda as a callback. We will use |
| 120 | // an io_context to obtain an associated executor. |
| 121 | std::move(op)( |
| 122 | boost::asio::bind_executor(ctx&: io_context, |
| 123 | t: [](const std::string& result) |
| 124 | { |
| 125 | std::cout << "Hello " << result << "\n" ; |
| 126 | })); |
| 127 | |
| 128 | io_context.run(); |
| 129 | } |
| 130 | |
| 131 | //------------------------------------------------------------------------------ |
| 132 | |
| 133 | void test_future() |
| 134 | { |
| 135 | // Test our asynchronous operation using the use_future completion token. |
| 136 | // This token causes the operation's initiating function to return a future, |
| 137 | // which may be used to synchronously wait for the result of the operation. |
| 138 | std::future<std::string> f = |
| 139 | async_read_input(prompt: "Enter your name" , token: boost::asio::use_future); |
| 140 | |
| 141 | std::string result = f.get(); |
| 142 | std::cout << "Hello " << result << "\n" ; |
| 143 | } |
| 144 | |
| 145 | //------------------------------------------------------------------------------ |
| 146 | |
| 147 | int main() |
| 148 | { |
| 149 | test_callback(); |
| 150 | test_deferred(); |
| 151 | test_future(); |
| 152 | } |
| 153 | |