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