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