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
19template <typename Callback>
20void 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.
38template <typename CompletionToken>
39auto 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
91void 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
109void 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
133void 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
147int main()
148{
149 test_callback();
150 test_deferred();
151 test_future();
152}
153

source code of boost/libs/asio/example/cpp14/operations/callback_wrapper.cpp