doc/html/boost_asio/example/cpp20/operations/callback_wrapper.cpp
//
// callback_wrapper.cpp
// ~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2024 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();
}