94.59% Lines (70/74) 100.00% Functions (24/24)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_NATIVE_NATIVE_TCP_SOCKET_HPP 10   #ifndef BOOST_COROSIO_NATIVE_NATIVE_TCP_SOCKET_HPP
11   #define BOOST_COROSIO_NATIVE_NATIVE_TCP_SOCKET_HPP 11   #define BOOST_COROSIO_NATIVE_NATIVE_TCP_SOCKET_HPP
12   12  
13   #include <boost/corosio/tcp_socket.hpp> 13   #include <boost/corosio/tcp_socket.hpp>
14   #include <boost/corosio/backend.hpp> 14   #include <boost/corosio/backend.hpp>
15   15  
16   #ifndef BOOST_COROSIO_MRDOCS 16   #ifndef BOOST_COROSIO_MRDOCS
17   #if BOOST_COROSIO_HAS_EPOLL 17   #if BOOST_COROSIO_HAS_EPOLL
18   #include <boost/corosio/native/detail/epoll/epoll_types.hpp> 18   #include <boost/corosio/native/detail/epoll/epoll_types.hpp>
19   #endif 19   #endif
20   20  
21   #if BOOST_COROSIO_HAS_SELECT 21   #if BOOST_COROSIO_HAS_SELECT
22   #include <boost/corosio/native/detail/select/select_types.hpp> 22   #include <boost/corosio/native/detail/select/select_types.hpp>
23   #endif 23   #endif
24   24  
25   #if BOOST_COROSIO_HAS_KQUEUE 25   #if BOOST_COROSIO_HAS_KQUEUE
26   #include <boost/corosio/native/detail/kqueue/kqueue_types.hpp> 26   #include <boost/corosio/native/detail/kqueue/kqueue_types.hpp>
27   #endif 27   #endif
28   28  
29   #if BOOST_COROSIO_HAS_IOCP 29   #if BOOST_COROSIO_HAS_IOCP
30   #include <boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp> 30   #include <boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp>
31   #endif 31   #endif
32   32  
33   #if BOOST_COROSIO_HAS_IO_URING 33   #if BOOST_COROSIO_HAS_IO_URING
34   #include <boost/corosio/native/detail/io_uring/io_uring_types.hpp> 34   #include <boost/corosio/native/detail/io_uring/io_uring_types.hpp>
35   #endif 35   #endif
36   #endif // !BOOST_COROSIO_MRDOCS 36   #endif // !BOOST_COROSIO_MRDOCS
37   37  
38   namespace boost::corosio { 38   namespace boost::corosio {
39   39  
40   /** An asynchronous TCP socket with devirtualized I/O operations. 40   /** An asynchronous TCP socket with devirtualized I/O operations.
41   41  
42   This class template inherits from @ref tcp_socket and shadows 42   This class template inherits from @ref tcp_socket and shadows
43   the async operations (`read_some`, `write_some`, `connect`) with 43   the async operations (`read_some`, `write_some`, `connect`) with
44   versions that call the backend implementation directly, allowing 44   versions that call the backend implementation directly, allowing
45   the compiler to inline through the entire call chain. 45   the compiler to inline through the entire call chain.
46   46  
47   Non-async operations (`open`, `close`, `cancel`, socket options) 47   Non-async operations (`open`, `close`, `cancel`, socket options)
48   remain unchanged and dispatch through the compiled library. 48   remain unchanged and dispatch through the compiled library.
49   49  
50   A `native_tcp_socket` IS-A `tcp_socket` and can be passed to 50   A `native_tcp_socket` IS-A `tcp_socket` and can be passed to
51   any function expecting `tcp_socket&` or `io_stream&`, in which 51   any function expecting `tcp_socket&` or `io_stream&`, in which
52   case virtual dispatch is used transparently. 52   case virtual dispatch is used transparently.
53   53  
54   @tparam Backend A backend tag value (e.g., `epoll`, 54   @tparam Backend A backend tag value (e.g., `epoll`,
55   `iocp`) whose type provides the concrete implementation 55   `iocp`) whose type provides the concrete implementation
56   types. 56   types.
57   57  
58   @par Thread Safety 58   @par Thread Safety
59   Same as @ref tcp_socket. 59   Same as @ref tcp_socket.
60   60  
61   @par Example 61   @par Example
62   @code 62   @code
63   #include <boost/corosio/native/native_tcp_socket.hpp> 63   #include <boost/corosio/native/native_tcp_socket.hpp>
64   64  
65   native_io_context<epoll> ctx; 65   native_io_context<epoll> ctx;
66   native_tcp_socket<epoll> s(ctx); 66   native_tcp_socket<epoll> s(ctx);
67   auto [ec] = co_await s.connect(ep); 67   auto [ec] = co_await s.connect(ep);
68   if (ec) 68   if (ec)
69   co_return; 69   co_return;
70   auto [ec2, n] = co_await s.read_some(buf); 70   auto [ec2, n] = co_await s.read_some(buf);
71   @endcode 71   @endcode
72   72  
73   @see tcp_socket, epoll_t, iocp_t 73   @see tcp_socket, epoll_t, iocp_t
74   */ 74   */
75   template<auto Backend> 75   template<auto Backend>
76   class native_tcp_socket : public tcp_socket 76   class native_tcp_socket : public tcp_socket
77   { 77   {
78   using backend_type = decltype(Backend); 78   using backend_type = decltype(Backend);
79   using impl_type = typename backend_type::tcp_socket_type; 79   using impl_type = typename backend_type::tcp_socket_type;
80   using service_type = typename backend_type::tcp_service_type; 80   using service_type = typename backend_type::tcp_service_type;
81   81  
HITCBC 82   33 impl_type& get_impl() noexcept 82   33 impl_type& get_impl() noexcept
83   { 83   {
HITCBC 84   33 return *static_cast<impl_type*>(h_.get()); 84   33 return *static_cast<impl_type*>(h_.get());
85   } 85   }
86   86  
87   template<class MutableBufferSequence> 87   template<class MutableBufferSequence>
88   struct native_read_awaitable 88   struct native_read_awaitable
89   { 89   {
90   native_tcp_socket& self_; 90   native_tcp_socket& self_;
91   MutableBufferSequence buffers_; 91   MutableBufferSequence buffers_;
92   std::stop_token token_; 92   std::stop_token token_;
93   mutable std::error_code ec_; 93   mutable std::error_code ec_;
94   mutable std::size_t bytes_transferred_ = 0; 94   mutable std::size_t bytes_transferred_ = 0;
95   95  
HITCBC 96   6 native_read_awaitable( 96   6 native_read_awaitable(
97   native_tcp_socket& self, MutableBufferSequence buffers) noexcept 97   native_tcp_socket& self, MutableBufferSequence buffers) noexcept
HITCBC 98   6 : self_(self) 98   6 : self_(self)
HITCBC 99   6 , buffers_(std::move(buffers)) 99   6 , buffers_(std::move(buffers))
100   { 100   {
HITCBC 101   6 } 101   6 }
102   102  
HITCBC 103   6 bool await_ready() const noexcept 103   6 bool await_ready() const noexcept
104   { 104   {
105   // A pre-set ec_ means the initiator failed before 105   // A pre-set ec_ means the initiator failed before
106   // dispatch (e.g. a closed object). 106   // dispatch (e.g. a closed object).
HITCBC 107   6 return static_cast<bool>(ec_) || token_.stop_requested(); 107   6 return static_cast<bool>(ec_) || token_.stop_requested();
108   } 108   }
109   109  
HITCBC 110   6 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept 110   6 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
111   { 111   {
HITCBC 112   6 if (token_.stop_requested()) 112   6 if (token_.stop_requested())
MISUBC 113   return {make_error_code(std::errc::operation_canceled), 0}; 113   return {make_error_code(std::errc::operation_canceled), 0};
HITCBC 114   6 return {ec_, bytes_transferred_}; 114   6 return {ec_, bytes_transferred_};
115   } 115   }
116   116  
HITCBC 117   6 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 117   6 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
118   -> std::coroutine_handle<> 118   -> std::coroutine_handle<>
119   { 119   {
HITCBC 120   6 token_ = env->stop_token; 120   6 token_ = env->stop_token;
HITCBC 121   18 return self_.get_impl().read_some( 121   18 return self_.get_impl().read_some(
HITCBC 122   18 h, env->executor, buffers_, token_, &ec_, &bytes_transferred_); 122   18 h, env->executor, buffers_, token_, &ec_, &bytes_transferred_);
123   } 123   }
124   }; 124   };
125   125  
126   template<class ConstBufferSequence> 126   template<class ConstBufferSequence>
127   struct native_write_awaitable 127   struct native_write_awaitable
128   { 128   {
129   native_tcp_socket& self_; 129   native_tcp_socket& self_;
130   ConstBufferSequence buffers_; 130   ConstBufferSequence buffers_;
131   std::stop_token token_; 131   std::stop_token token_;
132   mutable std::error_code ec_; 132   mutable std::error_code ec_;
133   mutable std::size_t bytes_transferred_ = 0; 133   mutable std::size_t bytes_transferred_ = 0;
134   134  
HITCBC 135   8 native_write_awaitable( 135   8 native_write_awaitable(
136   native_tcp_socket& self, ConstBufferSequence buffers) noexcept 136   native_tcp_socket& self, ConstBufferSequence buffers) noexcept
HITCBC 137   8 : self_(self) 137   8 : self_(self)
HITCBC 138   8 , buffers_(std::move(buffers)) 138   8 , buffers_(std::move(buffers))
139   { 139   {
HITCBC 140   8 } 140   8 }
141   141  
HITCBC 142   8 bool await_ready() const noexcept 142   8 bool await_ready() const noexcept
143   { 143   {
144   // A pre-set ec_ means the initiator failed before 144   // A pre-set ec_ means the initiator failed before
145   // dispatch (e.g. a closed object). 145   // dispatch (e.g. a closed object).
HITCBC 146   8 return static_cast<bool>(ec_) || token_.stop_requested(); 146   8 return static_cast<bool>(ec_) || token_.stop_requested();
147   } 147   }
148   148  
HITCBC 149   8 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept 149   8 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
150   { 150   {
HITCBC 151   8 if (token_.stop_requested()) 151   8 if (token_.stop_requested())
MISUBC 152   return {make_error_code(std::errc::operation_canceled), 0}; 152   return {make_error_code(std::errc::operation_canceled), 0};
HITCBC 153   8 return {ec_, bytes_transferred_}; 153   8 return {ec_, bytes_transferred_};
154   } 154   }
155   155  
HITCBC 156   8 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 156   8 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
157   -> std::coroutine_handle<> 157   -> std::coroutine_handle<>
158   { 158   {
HITCBC 159   8 token_ = env->stop_token; 159   8 token_ = env->stop_token;
HITCBC 160   24 return self_.get_impl().write_some( 160   24 return self_.get_impl().write_some(
HITCBC 161   24 h, env->executor, buffers_, token_, &ec_, &bytes_transferred_); 161   24 h, env->executor, buffers_, token_, &ec_, &bytes_transferred_);
162   } 162   }
163   }; 163   };
164   164  
165   struct native_wait_awaitable 165   struct native_wait_awaitable
166   { 166   {
167   native_tcp_socket& self_; 167   native_tcp_socket& self_;
168   wait_type w_; 168   wait_type w_;
169   std::stop_token token_; 169   std::stop_token token_;
170   mutable std::error_code ec_; 170   mutable std::error_code ec_;
171   171  
HITCBC 172   4 native_wait_awaitable(native_tcp_socket& self, wait_type w) noexcept 172   4 native_wait_awaitable(native_tcp_socket& self, wait_type w) noexcept
HITCBC 173   4 : self_(self) 173   4 : self_(self)
HITCBC 174   4 , w_(w) 174   4 , w_(w)
175   { 175   {
HITCBC 176   4 } 176   4 }
177   177  
HITCBC 178   4 bool await_ready() const noexcept 178   4 bool await_ready() const noexcept
179   { 179   {
180   // A pre-set ec_ means the initiator failed before 180   // A pre-set ec_ means the initiator failed before
181   // dispatch (e.g. a closed object). 181   // dispatch (e.g. a closed object).
HITCBC 182   4 return static_cast<bool>(ec_) || token_.stop_requested(); 182   4 return static_cast<bool>(ec_) || token_.stop_requested();
183   } 183   }
184   184  
HITCBC 185   4 [[nodiscard]] capy::io_result<> await_resume() const noexcept 185   4 [[nodiscard]] capy::io_result<> await_resume() const noexcept
186   { 186   {
HITCBC 187   4 if (token_.stop_requested()) 187   4 if (token_.stop_requested())
MISUBC 188   return {make_error_code(std::errc::operation_canceled)}; 188   return {make_error_code(std::errc::operation_canceled)};
HITCBC 189   4 return {ec_}; 189   4 return {ec_};
190   } 190   }
191   191  
HITCBC 192   4 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 192   4 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
193   -> std::coroutine_handle<> 193   -> std::coroutine_handle<>
194   { 194   {
HITCBC 195   4 token_ = env->stop_token; 195   4 token_ = env->stop_token;
HITCBC 196   12 return self_.get_impl().wait( 196   12 return self_.get_impl().wait(
HITCBC 197   12 h, env->executor, w_, token_, &ec_); 197   12 h, env->executor, w_, token_, &ec_);
198   } 198   }
199   }; 199   };
200   200  
201   struct native_connect_awaitable 201   struct native_connect_awaitable
202   { 202   {
203   native_tcp_socket& self_; 203   native_tcp_socket& self_;
204   endpoint endpoint_; 204   endpoint endpoint_;
205   std::stop_token token_; 205   std::stop_token token_;
206   mutable std::error_code ec_; 206   mutable std::error_code ec_;
207   207  
HITCBC 208   15 native_connect_awaitable(native_tcp_socket& self, endpoint ep) noexcept 208   15 native_connect_awaitable(native_tcp_socket& self, endpoint ep) noexcept
HITCBC 209   15 : self_(self) 209   15 : self_(self)
HITCBC 210   15 , endpoint_(ep) 210   15 , endpoint_(ep)
211   { 211   {
HITCBC 212   15 } 212   15 }
213   213  
HITCBC 214   15 bool await_ready() const noexcept 214   15 bool await_ready() const noexcept
215   { 215   {
216   // A pre-set ec_ means the initiator failed before 216   // A pre-set ec_ means the initiator failed before
217   // dispatch (e.g. a closed object). 217   // dispatch (e.g. a closed object).
HITCBC 218   15 return static_cast<bool>(ec_) || token_.stop_requested(); 218   15 return static_cast<bool>(ec_) || token_.stop_requested();
219   } 219   }
220   220  
HITCBC 221   15 [[nodiscard]] capy::io_result<> await_resume() const noexcept 221   15 [[nodiscard]] capy::io_result<> await_resume() const noexcept
222   { 222   {
HITCBC 223   15 if (token_.stop_requested()) 223   15 if (token_.stop_requested())
MISUBC 224   return {make_error_code(std::errc::operation_canceled)}; 224   return {make_error_code(std::errc::operation_canceled)};
HITCBC 225   15 return {ec_}; 225   15 return {ec_};
226   } 226   }
227   227  
HITCBC 228   15 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 228   15 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
229   -> std::coroutine_handle<> 229   -> std::coroutine_handle<>
230   { 230   {
HITCBC 231   15 token_ = env->stop_token; 231   15 token_ = env->stop_token;
HITCBC 232   45 return self_.get_impl().connect( 232   45 return self_.get_impl().connect(
HITCBC 233   45 h, env->executor, endpoint_, token_, &ec_); 233   45 h, env->executor, endpoint_, token_, &ec_);
234   } 234   }
235   }; 235   };
236   236  
237   public: 237   public:
238   /** Construct a native socket from an execution context. 238   /** Construct a native socket from an execution context.
239   239  
240   @param ctx The execution context that will own this socket. 240   @param ctx The execution context that will own this socket.
241   */ 241   */
HITCBC 242   35 explicit native_tcp_socket(capy::execution_context& ctx) 242   35 explicit native_tcp_socket(capy::execution_context& ctx)
HITCBC 243   35 : io_object(create_handle<service_type>(ctx)) 243   35 : io_object(create_handle<service_type>(ctx))
244   { 244   {
HITCBC 245   35 } 245   35 }
246   246  
247   /** Construct a native socket from an executor. 247   /** Construct a native socket from an executor.
248   248  
249   @param ex The executor whose context will own the socket. 249   @param ex The executor whose context will own the socket.
250   */ 250   */
251   template<class Ex> 251   template<class Ex>
252   requires(!std::same_as<std::remove_cvref_t<Ex>, native_tcp_socket>) && 252   requires(!std::same_as<std::remove_cvref_t<Ex>, native_tcp_socket>) &&
253   capy::Executor<Ex> 253   capy::Executor<Ex>
254   explicit native_tcp_socket(Ex const& ex) : native_tcp_socket(ex.context()) 254   explicit native_tcp_socket(Ex const& ex) : native_tcp_socket(ex.context())
255   { 255   {
256   } 256   }
257   257  
258   /** Move construct. 258   /** Move construct.
259   259  
260   @param other The socket to move from. 260   @param other The socket to move from.
261   261  
262   @pre No awaitables returned by @p other's methods exist. 262   @pre No awaitables returned by @p other's methods exist.
263   @pre @p other is not referenced as a peer in any outstanding 263   @pre @p other is not referenced as a peer in any outstanding
264   accept awaitable. 264   accept awaitable.
265   @pre The execution context associated with @p other must 265   @pre The execution context associated with @p other must
266   outlive this socket. 266   outlive this socket.
267   */ 267   */
HITCBC 268   16 native_tcp_socket(native_tcp_socket&&) noexcept = default; 268   16 native_tcp_socket(native_tcp_socket&&) noexcept = default;
269   269  
270   /** Move assign. 270   /** Move assign.
271   271  
272   @param other The socket to move from. 272   @param other The socket to move from.
273   273  
274   @pre No awaitables returned by either `*this` or @p other's 274   @pre No awaitables returned by either `*this` or @p other's
275   methods exist. 275   methods exist.
276   @pre Neither `*this` nor @p other is referenced as a peer in 276   @pre Neither `*this` nor @p other is referenced as a peer in
277   any outstanding accept awaitable. 277   any outstanding accept awaitable.
278   @pre The execution context associated with @p other must 278   @pre The execution context associated with @p other must
279   outlive this socket. 279   outlive this socket.
280   */ 280   */
HITCBC 281   3 native_tcp_socket& operator=(native_tcp_socket&&) noexcept = default; 281   3 native_tcp_socket& operator=(native_tcp_socket&&) noexcept = default;
282   282  
283   native_tcp_socket(native_tcp_socket const&) = delete; 283   native_tcp_socket(native_tcp_socket const&) = delete;
284   native_tcp_socket& operator=(native_tcp_socket const&) = delete; 284   native_tcp_socket& operator=(native_tcp_socket const&) = delete;
285   285  
286   /** Asynchronously read data from the socket. 286   /** Asynchronously read data from the socket.
287   287  
288   Calls the backend implementation directly, bypassing virtual 288   Calls the backend implementation directly, bypassing virtual
289   dispatch. Otherwise identical to @ref io_stream::read_some. 289   dispatch. Otherwise identical to @ref io_stream::read_some.
290   290  
291   @param buffers The buffer sequence to read into. 291   @param buffers The buffer sequence to read into.
292   292  
293   @return An awaitable yielding `(error_code, std::size_t)`. 293   @return An awaitable yielding `(error_code, std::size_t)`.
294   294  
295   This socket must outlive the returned awaitable. The memory 295   This socket must outlive the returned awaitable. The memory
296   referenced by @p buffers must remain valid until the operation 296   referenced by @p buffers must remain valid until the operation
297   completes. 297   completes.
298   */ 298   */
299   template<capy::MutableBufferSequence MB> 299   template<capy::MutableBufferSequence MB>
HITCBC 300   6 [[nodiscard]] auto read_some(MB const& buffers) 300   6 [[nodiscard]] auto read_some(MB const& buffers)
301   { 301   {
HITCBC 302   6 return native_read_awaitable<MB>(*this, buffers); 302   6 return native_read_awaitable<MB>(*this, buffers);
303   } 303   }
304   304  
305   /** Asynchronously write data to the socket. 305   /** Asynchronously write data to the socket.
306   306  
307   Calls the backend implementation directly, bypassing virtual 307   Calls the backend implementation directly, bypassing virtual
308   dispatch. Otherwise identical to @ref io_stream::write_some. 308   dispatch. Otherwise identical to @ref io_stream::write_some.
309   309  
310   @param buffers The buffer sequence to write from. 310   @param buffers The buffer sequence to write from.
311   311  
312   @return An awaitable yielding `(error_code, std::size_t)`. 312   @return An awaitable yielding `(error_code, std::size_t)`.
313   313  
314   This socket must outlive the returned awaitable. The memory 314   This socket must outlive the returned awaitable. The memory
315   referenced by @p buffers must remain valid until the operation 315   referenced by @p buffers must remain valid until the operation
316   completes. 316   completes.
317   */ 317   */
318   template<capy::ConstBufferSequence CB> 318   template<capy::ConstBufferSequence CB>
HITCBC 319   8 [[nodiscard]] auto write_some(CB const& buffers) 319   8 [[nodiscard]] auto write_some(CB const& buffers)
320   { 320   {
HITCBC 321   8 return native_write_awaitable<CB>(*this, buffers); 321   8 return native_write_awaitable<CB>(*this, buffers);
322   } 322   }
323   323  
324   /** Asynchronously connect to a remote endpoint. 324   /** Asynchronously connect to a remote endpoint.
325   325  
326   Calls the backend implementation directly, bypassing virtual 326   Calls the backend implementation directly, bypassing virtual
327   dispatch. Otherwise identical to @ref tcp_socket::connect. 327   dispatch. Otherwise identical to @ref tcp_socket::connect.
328   328  
329   If the socket is not open, it is opened automatically using 329   If the socket is not open, it is opened automatically using
330   the protocol matching the endpoint's address family. An open 330   the protocol matching the endpoint's address family. An open
331   failure surfaces through the connect completion. 331   failure surfaces through the connect completion.
332   332  
333   @param ep The remote endpoint to connect to. 333   @param ep The remote endpoint to connect to.
334   334  
335   @return An awaitable yielding `io_result<>`. 335   @return An awaitable yielding `io_result<>`.
336   336  
337   This socket must outlive the returned awaitable. 337   This socket must outlive the returned awaitable.
338   */ 338   */
HITCBC 339   15 [[nodiscard]] auto connect(endpoint ep) 339   15 [[nodiscard]] auto connect(endpoint ep)
340   { 340   {
HITCBC 341   15 native_connect_awaitable aw(*this, ep); 341   15 native_connect_awaitable aw(*this, ep);
HITCBC 342   15 if (!is_open()) 342   15 if (!is_open())
HITCBC 343   2 aw.ec_ = open(ep.is_v6() ? tcp::v6() : tcp::v4()); 343   2 aw.ec_ = open(ep.is_v6() ? tcp::v6() : tcp::v4());
HITCBC 344   15 return aw; 344   15 return aw;
345   } 345   }
346   346  
347   /** Asynchronously wait for the socket to be ready. 347   /** Asynchronously wait for the socket to be ready.
348   348  
349   Calls the backend implementation directly, bypassing virtual 349   Calls the backend implementation directly, bypassing virtual
350   dispatch. Otherwise identical to @ref tcp_socket::wait. 350   dispatch. Otherwise identical to @ref tcp_socket::wait.
351   351  
352   @param w The wait direction (read, write, or error). 352   @param w The wait direction (read, write, or error).
353   353  
354   @return An awaitable yielding `io_result<>`. 354   @return An awaitable yielding `io_result<>`.
355   */ 355   */
HITCBC 356   4 [[nodiscard]] auto wait(wait_type w) 356   4 [[nodiscard]] auto wait(wait_type w)
357   { 357   {
HITCBC 358   4 return native_wait_awaitable(*this, w); 358   4 return native_wait_awaitable(*this, w);
359   } 359   }
360   }; 360   };
361   361  
362   } // namespace boost::corosio 362   } // namespace boost::corosio
363   363  
364   #endif 364   #endif