Document number: P1322R1 Date: 2020-01-13 Project: Programming Language C++ Audience: SG4 - Networking, SG1 - Concurrency and Parallelism, LEWG Reply-to: Christopher Kohlhoff <chris@kohlhoff.com>
At present, the Networking TS's I/O objects -- sockets, acceptors, resolvers, and timers -- can be associated only with concrete execution contexts of type io_context. This paper proposes a minor change to the specification to permit the construction of these objects with arbitrary I/O executors and execution contexts. This would:
Users and third-party libraries may implement their own execution contexts, for example to provide different scheduling guarantees, or to natively support other types of I/O objects, such as files. This proposal would allow users to create the standard I/O objects, such as sockets, to be associated with these custom execution contexts. However, although constructing the socket with a custom execution context will provide the correct, expected behaviour, there is no guarantee that it will operate as efficiently as a socket associated with a native execution context.
Some operating systems have multiple potential implementation strategies for the I/O objects, like sockets. For example, on Windows we can choose to have asynchronous notifications delivered via a completion port to a user-created thread, or we can have them delivered directly to the system thread pool. An implementer may want to make both of these strategies available, via the io_context and system_context execution contexts, respectively. For example:
io_context my_context;
tcp::socket my_socket_1(my_context); // Implementation uses a completion port.
// ...
tcp::socket my_socket_2(system_executor{}); // Implementation uses the system thread pool.
Libraries can also provide additional native execution contexts as implementation-specific extensions.
In some use cases, all asynchronous operations on a given I/O object are performed using the same executor. One common example of this is when using a strand. This proposal allows this executor to be specified once, when the I/O object is constructed. For example:
io_context my_context; // ... strand<io_context::executor_type> my_strand(my_context.get_executor()); tcp::socket my_socket(my_strand); // ... my_socket.async_receive(my_buffer, my_handler); // my_handler is invoked in the strand
The Executor template parameter also offers an opportunity to specialize the I/O object types for specific executor types. For example, some platforms support vendor-specific kernel bypass APIs for I/O such as sockets, enabling significantly lower latency and higher throughput when compared to system calls. Furthermore, these APIs are often limited to supporting only TCP and UDP. A library or user may therefore choose to define an executor type that represents this API as a venue for I/O execution:
class vendor_xyz_kernel_bypass_executor
{
// ...
};
and specialize the socket templates for this executor type:
template<>
class basic_socket<ip::tcp, vendor_xyz_kernel_bypass_executor>
{
// ...
};
template<>
class basic_datagram_socket<ip::tcp, vendor_xyz_kernel_bypass_executor>
: public basic_socket<ip::tcp, vendor_xyz_kernel_bypass_executor>
{
// ...
template<class ConstBufferSequence>
void send(const ConstBufferSequence& buffers)
{
// Implemented using vendor_xyz_send().
// ...
}
// ...
};
A new Executor template parameter is added to the following templates:
basic_waitable_timerbasic_socketbasic_datagram_socketbasic_stream_socketbasic_socket_acceptorbasic_socket_streambufbasic_socket_iostreamip::basic_resolverTo enable convenient interoperability with arbitrary executors and execution contexts, the template parameter is defaulted to the executor polymorphic wrapper. For example:
template <class Protocol, class Executor = executor>
class basic_socket;
For each of:
basic_waitable_timerbasic_socketbasic_socket_acceptorip::basic_resolverthe existing nested type executor_type is altered to refer to the Executor type:
template <class Protocol, class Executor>
class basic_socket : public socket_base
{
public:
using executor_type = io_context::executor_typeExecutor;
// ...
};
For the remaining four templates:
basic_datagram_socketbasic_stream_socketbasic_socket_streambufbasic_socket_iostreamthe nested type executor_type is added:
template <class Protocol, class Executor>
class basic_stream_socket : public basic_socket<Protocol, Executor>
{
public:
using executor_type = Executor;
// ...
};
For each of the following templates:
basic_waitable_timerbasic_socketbasic_datagram_socketbasic_stream_socketbasic_socket_acceptorip::basic_resolvera nested template type rebind_executor is added:
template<class Protocol, class Executor>
class basic_socket : public socket_base
{
// ...
template <typename OtherExecutor>
struct rebind_executor
{
/// The socket type when rebound to the specified executor.
typedef basic_socket<Protocol, OtherExecutor> other;
};
// ...
};
This enables computation of the appropriate class type for different I/O executor types.
For each of the following templates:
basic_waitable_timerbasic_socketbasic_datagram_socketbasic_stream_socketbasic_socket_acceptorip::basic_resolverevery constructor with an io_context& parameter is replaced with two constructors:
template<class Protocol, class Executor>
class basic_socket : public socket_base
{
// ...
basic_socket(io_context& ctx, const protocol_type& protocol);
basic_socket(const executor_type& ex, const protocol_type& protocol);
template<class ExecutionContext>
basic_socket(ExecutionContext& ctx, const protocol_type& protocol);
// ...
};
The second of these constructors shall not participate in overload resolution unless is_convertible<ExecutionContext&, execution_context&>::value is true, and is_constructible<executor_type, typename ExecutionContext::executor_type>::value is true.
An OtherExecutor template parameter is added as required to the converting move constructors of the following classes:
basic_socketbasic_datagram_socketbasic_stream_socketbasic_socket_acceptorFor example:
template<class Protocol, class Executor>
class basic_socket : public socket_base
{
// ...
template<class OtherProtocol, class OtherExecutor>
basic_socket(basic_socket<OtherProtocol, OtherExecutor>&& rhs);
// ...
};
This constructor shall not participate in overload resolution unless OtherProtocol is implicitly convertible to Protocol, and OtherExecutor is implicitly convertible to Executor.
An OtherExecutor template parameter is added as required to the converting move assignment operators of the following classes:
basic_socketbasic_datagram_socketbasic_stream_socketbasic_socket_acceptorFor example:
template<class Protocol, class Executor>
class basic_socket : public socket_base
{
// ...
template<class OtherProtocol, class OtherExecutor>
basic_socket& operator=(basic_socket<OtherProtocol, OtherExecutor>&& rhs);
// ...
};
This assignment operator shall not participate in overload resolution unless OtherProtocol is implicitly convertible to Protocol, and OtherExecutor is implicitly convertible to Executor.
Every overload of member functions accept and async_accept with an io_context& parameter is replaced with two overloads:
template<class AcceptableProtocol, class Executor>
class basic_socket_acceptor : public socket_base
{
// ...
socket_type accept(io_context& ctx);
socket_type accept(const executor_type& ex);
template<class ExecutionContext>
socket_type accept(ExecutionContext& ctx);
// ...
};
The second of these overloads shall not participate in overload resolution unless is_convertible<ExecutionContext&, execution_context&>::value is true, and is_constructible<executor_type, typename ExecutionContext::executor_type>::value is true.
An Executor template parameter is added as required to the connect and async_connect functions. For example:
template<class Protocol, class Executor, class EndpointSequence>
typename Protocol::endpoint connect(basic_socket<Protocol, Executor>& s,
const EndpointSequence& endpoints);
The basic_socket_streambuf default constructor is modified so that it shall not participate in overload resolution unless is_constructible<executor_type, io_context::executor_type>::value is true.
The basic_socket_iostream specification is modified so that the executor_type type is passed to all uses of basic_socket_streambuf, as required. The basic_socket_iostream default constructor is modified so that it shall not participate in overload resolution unless is_default_constructible<basic_socket_streambuf<protocol_type, clock_type, wait_traits_type, executor_type>>::value is true.
This section is intended to provide implementation suggestions only, and is not intended to be prescriptive or exhaustive.
Implementations may use an execution context service as a container for the backend implementation.
class __socket_backend : public execution_context::service
{
// ...
};
This service would be "used" by an I/O object implementation, and is automatically created on first use:
template <class Protocol, class Executor>
class basic_socket : public socket_base
{
public:
using executor_type = Executor;
// ...
explicit basic_socket(const executor_type& ex)
{
auto& backend = use_service<__socket_backend>(ex.context());
// ...
}
// ...
};
This allows the I/O object to be used with arbitrary execution contexts.
A native execution context preemptively performs service creation by calling make_service. It can use this opportunity to pass additional constructor arguments that initialise the backend in the native mode, rather than the default:
class io_context : public execution_context
{
public:
// ...
io_context()
{
make_service<__socket_backend>(*this, __io_context_backend_tag{});
}
// ...
};
Alternatively, implementations may use a class hierarchy of services, and virtual functions, to select the desired behaviour:
class __socket_backend : public execution_context::service
{
public:
using key_type = __socket_backend;
// ...
virtual void backend_function(/* ... */);
// ...
};
class __io_socket_backend : public __socket_backend
{
public:
// ...
void backend_function(/* ... */) override;
// ...
};
class io_context : public execution_context
{
public:
// ...
io_context()
{
make_service<__io_socket_backend>(*this);
}
// ...
};
In both approaches, the existing service, with its native backend, is obtained by the I/O object constructor.
Specification of the polymorphic executor as the default I/O executor, while improving usability, has a non-zero impact on performance. This impact can be mitigated by having an I/O object's constructor detect its own well-known native executor types (e.g. by using executor::target_type). With this information, the overhead can then be limited to simple branching, rather than the memory allocations and virtual functions that would likely be required for the type-erased function objects.
Users can avoid this overhead completely by explicitly specifying the I/O executor type:
io_context my_context; basic_stream_socket<tcp, io_context::executor_type> my_socket(my_context);
This design change was implemented in Asio 1.13.0, and was delivered in the Boost 1.70 release as the stable Asio version 1.14.0.
These changes are relative to N4771.
Update the <experimental/netfwd> synopsis [fwd.decl.synop] as follows:
namespace std { namespace experimental { namespace net { inline namespace v1 { class execution_context; template<class T, class Executor> class executor_binder; template<class Executor> class executor_work_guard; class system_executor; class executor; template<class Executor> class strand; class io_context; template<class Clock> struct wait_traits; template<class Clock, class WaitTraits = wait_traits<Clock>, class Executor = executor> class basic_waitable_timer; using system_timer = basic_waitable_timer<chrono::system_clock>; using steady_timer = basic_waitable_timer<chrono::steady_clock>; using high_resolution_timer = basic_waitable_timer<chrono::high_resolution_clock>; template<class Protocol, class Executor = executor> class basic_socket; template<class Protocol, class Executor = executor> class basic_datagram_socket; template<class Protocol, class Executor = executor> class basic_stream_socket; template<class Protocol, class Executor = executor> class basic_socket_acceptor; template<class Protocol, class Clock = chrono::steady_clock, class WaitTraits = wait_traits<Clock>, class Executor = executor> class basic_socket_streambuf; template<class Protocol, class Clock = chrono::steady_clock, class WaitTraits = wait_traits<Clock>, class Executor = executor> class basic_socket_iostream; namespace ip { class address; class address_v4; class address_v6; template<class Address> class basic_address_iterator; using address_v4_iterator = basic_address_iterator<address_v4>; using address_v6_iterator = basic_address_iterator<address_v6>; template<class Address> class basic_address_range; using address_v4_range = basic_address_range<address_v4>; using address_v6_range = basic_address_range<address_v6>; class network_v4; class network_v6; template<class InternetProtocol> class basic_endpoint; template<class InternetProtocol> class basic_resolver_entry; template<class InternetProtocol> class basic_resolver_results; template<class InternetProtocol, class Executor = executor> class basic_resolver; class tcp; class udp; } // namespace ip } // inline namespace v1 } // namespace net } // namespace experimental } // namespace std
Update the <experimental/timer> synopsis [timer.synop] as follows:
[...]
template<class Clock, class WaitTraits = wait_traits<Clock>, class Executor = executor> class basic_waitable_timer;
Update the class template basic_waitable_timer [timer.waitable] as follows:
namespace std { namespace experimental { namespace net { inline namespace v1 { template<class Clock, class WaitTraits = wait_traits<Clock>, class Executor = executor> class basic_waitable_timer { public: // types: using executor_type =io_context::executor_typeExecutor; using clock_type = Clock; using duration = typename clock_type::duration; using time_point = typename clock_type::time_point; using traits_type = WaitTraits; template<class OtherExecutor> struct rebind_executor { using other = basic_waitable_timer<Clock, WaitTraits, OtherExecutor>; }; // [timer.waitable.cons], construct / copy / destroy: explicit basic_waitable_timer(io_context& ctxconst executor_type& ex); template<class ExecutionContext> explicit basic_waitable_timer(ExecutionContext& ctx); basic_waitable_timer(io_context& ctxconst executor_type& ex, const time_point& t); template<class ExecutionContext> basic_waitable_timer(ExecutionContext& ctx, const time_point& t); basic_waitable_timer(io_context& ctxconst executor_type& ex, const duration& d); template<class ExecutionContext> basic_waitable_timer(ExecutionContext& ctx, const duration& d); basic_waitable_timer(const basic_waitable_timer&) = delete; basic_waitable_timer(basic_waitable_timer&& rhs);
Update the basic_waitable_timer constructors [timer.waitable.cons] as follows:
explicit basic_waitable_timer(io_context& ctxconst executor_type& ex);
template<class ExecutionContext> explicit basic_waitable_timer(ExecutionContext& ctx);
basic_waitable_timer(io_context& ctxconst executor_type& ex, const time_point& t);
template<class ExecutionContext> basic_waitable_timer(ExecutionContext& ctx, const time_point& t);
basic_waitable_timer(io_context& ctxconst executor_type& ex, const duration& d);
template<class ExecutionContext> basic_waitable_timer(ExecutionContext& ctx, const duration& d);
Update the <experimental/socket> synopsis [socket.synop] as follows:
namespace std { namespace experimental { namespace net { inline namespace v1 { enum class socket_errc { already_open = an implementation-defined non-zero value, not_found = an implementation-defined non-zero value }; const error_category& socket_category() noexcept; error_code make_error_code(socket_errc e) noexcept; error_condition make_error_condition(socket_errc e) noexcept; // Sockets: class socket_base; template<class Protocol, class Executor = executor> class basic_socket; template<class Protocol, class Executor = executor> class basic_datagram_socket; template<class Protocol, class Executor = executor> class basic_stream_socket; template<class Protocol, class Executor = executor> class basic_socket_acceptor; // [socket.iostreams], Socket streams: template<class Protocol, class Clock = chrono::steady_clock, class WaitTraits = wait_traits<Clock>, class Executor = executor> class basic_socket_streambuf; template<class Protocol, class Clock = chrono::steady_clock, class WaitTraits = wait_traits<Clock>, class Executor = executor> class basic_socket_iostream; // [socket.algo.connect], synchronous connect operations: template<class Protocol, class Executor, class EndpointSequence> typename Protocol::endpoint connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints); template<class Protocol, class Executor, class EndpointSequence> typename Protocol::endpoint connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints, error_code& ec); template<class Protocol, class Executor, class EndpointSequence, class ConnectCondition> typename Protocol::endpoint connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints, ConnectCondition c); template<class Protocol, class Executor, class EndpointSequence, class ConnectCondition> typename Protocol::endpoint connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints, ConnectCondition c, error_code& ec); template<class Protocol, class Executor, class InputIterator> InputIterator connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last); template<class Protocol, class Executor, class InputIterator> InputIterator connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last, error_code& ec); template<class Protocol, class Executor, class InputIterator, class ConnectCondition> InputIterator connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last, ConnectCondition c); template<class Protocol, class Executor, class InputIterator, class ConnectCondition> InputIterator connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last, ConnectCondition c, error_code& ec); // [socket.algo.async.connect], asynchronous connect operations: template<class Protocol, class Executor, class EndpointSequence, class CompletionToken> DEDUCED async_connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints, CompletionToken&& token); template<class Protocol, class Executor, class EndpointSequence, class ConnectCondition, class CompletionToken> DEDUCED async_connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints, ConnectCondition c, CompletionToken&& token); template<class Protocol, class Executor, class InputIterator, class CompletionToken> DEDUCED async_connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last, CompletionToken&& token); template<class Protocol, class Executor, class InputIterator, class ConnectCondition, class CompletionToken> DEDUCED async_connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last, ConnectCondition c, CompletionToken&& token); } // inline namespace v1 } // namespace net } // namespace experimental template<> struct is_error_code_enum<experimental::net::v1::socket_errc> : public true_type {}; } // namespace std
Update the class socket_base [socket.base] as follows:
namespace std { namespace experimental { namespace net { inline namespace v1 { class socket_base { public: class broadcast; class debug; class do_not_route; class keep_alive; class linger; class out_of_band_inline; class receive_buffer_size; class receive_low_watermark; class reuse_address; class send_buffer_size; class send_low_watermark; using shutdown_type = T1; static constexpr shutdown_type shutdown_receive; static constexpr shutdown_type shutdown_send; static constexpr shutdown_type shutdown_both; using wait_type = T2; static constexpr wait_type wait_read; static constexpr wait_type wait_write; static constexpr wait_type wait_error; using message_flags = T3; static constexpr message_flags message_peek; static constexpr message_flags message_out_of_band; static constexpr message_flags message_do_not_route; static const int max_listen_connections; protected: socket_base(); ~socket_base(); }; } // inline namespace v1 } // namespace net } // namespace experimental } // namespace std
Update the class template basic_socket [socket.basic] as follows:
namespace std { namespace experimental { namespace net { inline namespace v1 { template<class Protocol, class Executor> class basic_socket : public socket_base { public: // types: using executor_type =io_context::executor_typeExecutor; using native_handle_type = implementation-defined; // see [socket.reqmts.native] using protocol_type = Protocol; using endpoint_type = typename protocol_type::endpoint; template<class OtherExecutor> struct rebind_executor { using other = basic_socket<Protocol, OtherExecutor>; }; // [socket.basic.ops], basic_socket operations: executor_type get_executor() noexcept; native_handle_type native_handle(); // see [socket.reqmts.native] void open(const protocol_type& protocol = protocol_type()); void open(const protocol_type& protocol, error_code& ec); void assign(const protocol_type& protocol, const native_handle_type& native_socket); // see [socket.reqmts.native] void assign(const protocol_type& protocol, const native_handle_type& native_socket, error_code& ec); // see [socket.reqmts.native] native_handle_type release(); // see [socket.reqmts.native] native_handle_type release(error_code& ec); // see [socket.reqmts.native] bool is_open() const noexcept; void close(); void close(error_code& ec); void cancel(); void cancel(error_code& ec); template<class SettableSocketOption> void set_option(const SettableSocketOption& option); template<class SettableSocketOption> void set_option(const SettableSocketOption& option, error_code& ec); template<class GettableSocketOption> void get_option(GettableSocketOption& option) const; template<class GettableSocketOption> void get_option(GettableSocketOption& option, error_code& ec) const; template<class IoControlCommand> void io_control(IoControlCommand& command); template<class IoControlCommand> void io_control(IoControlCommand& command, error_code& ec); void non_blocking(bool mode); void non_blocking(bool mode, error_code& ec); bool non_blocking() const; void native_non_blocking(bool mode); void native_non_blocking(bool mode, error_code& ec); bool native_non_blocking() const; bool at_mark() const; bool at_mark(error_code& ec) const; size_t available() const; size_t available(error_code& ec) const; void bind(const endpoint_type& endpoint); void bind(const endpoint_type& endpoint, error_code& ec); void shutdown(shutdown_type what); void shutdown(shutdown_type what, error_code& ec); endpoint_type local_endpoint() const; endpoint_type local_endpoint(error_code& ec) const; endpoint_type remote_endpoint() const; endpoint_type remote_endpoint(error_code& ec) const; void connect(const endpoint_type& endpoint); void connect(const endpoint_type& endpoint, error_code& ec); template<class CompletionToken> DEDUCED async_connect(const endpoint_type& endpoint, CompletionToken&& token); void wait(wait_type w); void wait(wait_type w, error_code& ec); template<class CompletionToken> DEDUCED async_wait(wait_type w, CompletionToken&& token); protected: // [socket.basic.cons], construct / copy / destroy: explicit basic_socket(io_context& ctxconst executor_type& ex); template<class ExecutionContext> explicit basic_socket(ExecutionContext& ctx); basic_socket(io_context& ctxconst executor_type& ex, const protocol_type& protocol); template<class ExecutionContext> basic_socket(ExecutionContext& ctx, const protocol_type& protocol); basic_socket(io_context& ctxconst executor_type& ex, const endpoint_type& endpoint); template<class ExecutionContext> basic_socket(ExecutionContext& ctx, const endpoint_type& endpoint); basic_socket(io_context& ctxconst executor_type& ex, const protocol_type& protocol, const native_handle_type& native_socket); // see [socket.reqmts.native] template<class ExecutionContext> basic_socket(ExecutionContext& ctx, const protocol_type& protocol, const native_handle_type& native_socket); // see [socket.reqmts.native] basic_socket(const basic_socket&) = delete; basic_socket(basic_socket&& rhs); template<class OtherProtocol, class OtherExecutor> basic_socket(basic_socket<OtherProtocol, OtherExecutor>&& rhs); ~basic_socket(); basic_socket& operator=(const basic_socket&) = delete; basic_socket& operator=(basic_socket&& rhs); template<class OtherProtocol, class OtherExecutor> basic_socket& operator=(basic_socket<OtherProtocol, OtherExecutor>&& rhs); private: protocol_type protocol_; // exposition only }; } // inline namespace v1 } // namespace net } // namespace experimental } // namespace std
Update the basic_socket constructors [socket.basic.cons] as follows:
explicit basic_socket(io_context& ctxconst executor_type& ex);
template<class ExecutionContext> explicit basic_socket(ExecutionContext& ctx);
basic_socket(io_context& ctxconst executor_type& ex, const protocol_type& protocol);
template<class ExecutionContext> explicit basic_socket(ExecutionContext& ctx, const protocol_type& protocol);
basic_socket(io_context& ctxconst executor_type& ex, const endpoint_type& endpoint);
template<class ExecutionContext> explicit basic_socket(ExecutionContext& ctx, const endpoint_type& endpoint);
basic_socket(io_context& ctxconst executor_type& ex, const protocol_type& protocol, const native_handle_type& native_socket);
template<class ExecutionContext> basic_socket(ExecutionContext& ctx, const protocol_type& protocol, const native_handle_type& native_socket);
basic_socket(basic_socket&& rhs);
template<class OtherProtocol, class OtherExecutor> basic_socket(basic_socket<OtherProtocol, OtherExecutor>&& rhs);
Update the basic_socket assignment operators [socket.basic.assign] as follows:
basic_socket& operator=(basic_socket&& rhs);
template<class OtherProtocol, class OtherExecutor> basic_socket& operator=(basic_socket<OtherProtocol, OtherExecutor>&& rhs);
Update the class template basic_datagram_socket [socket.basic] as follows:
namespace std { namespace experimental { namespace net { inline namespace v1 { template<class Protocol, class Executor> class basic_datagram_socket : public basic_socket<Protocol, Executor> { public: // types: using executor_type = Executor; using native_handle_type = implementation-defined; // see [socket.reqmts.native] using protocol_type = Protocol; using endpoint_type = typename protocol_type::endpoint; template<class OtherExecutor> struct rebind_executor { using other = basic_datagram_socket<Protocol, OtherExecutor>; }; // [socket.dgram.cons], construct / copy / destroy: explicit basic_datagram_socket(io_context& ctxconst executor_type& ex); template<class ExecutionContext> explicit basic_datagram_socket(ExecutionContext& ctx); basic_datagram_socket(io_context& ctxconst executor_type& ex, const protocol_type& protocol); template<class ExecutionContext> basic_datagram_socket(ExecutionContext& ctx, const protocol_type& protocol); basic_datagram_socket(io_context& ctxconst executor_type& ex, const endpoint_type& endpoint); template<class ExecutionContext> basic_datagram_socket(ExecutionContext& ctx, const endpoint_type& endpoint); basic_datagram_socket(io_context& ctxconst executor_type& ex, const protocol_type& protocol, const native_handle_type& native_socket); template<class ExecutionContext> basic_datagram_socket(ExecutionContext& ctx, const protocol_type& protocol, const native_handle_type& native_socket); basic_datagram_socket(const basic_datagram_socket&) = delete; basic_datagram_socket(basic_datagram_socket&& rhs); template<class OtherProtocol, class OtherExecutor> basic_datagram_socket(basic_datagram_socket<OtherProtocol, OtherExecutor>&& rhs); ~basic_datagram_socket(); basic_datagram_socket& operator=(const basic_datagram_socket&) = delete; basic_datagram_socket& operator=(basic_datagram_socket&& rhs); template<class OtherProtocol, class OtherExecutor> basic_datagram_socket& operator=(basic_datagram_socket<OtherProtocol, OtherExecutor>&& rhs);
[...]
Update the basic_datagram_socket constructors [socket.dgram.cons] as follows:
explicit basic_datagram_socket(io_context& ctxconst executor_type& ex);
template<class ExecutionContext> explicit basic_datagram_socket(ExecutionContext& ctx);
basic_datagram_socket(io_context& ctxconst executor_type& ex, const protocol_type& protocol);
template<class ExecutionContext> explicit basic_datagram_socket(ExecutionContext& ctx, const protocol_type& protocol);
basic_datagram_socket(io_context& ctxconst executor_type& ex, const endpoint_type& endpoint);
template<class ExecutionContext> explicit basic_datagram_socket(ExecutionContext& ctx, const endpoint_type& endpoint);
basic_datagram_socket(io_context& ctx, const protocol_type& protocol, const native_handle_type& native_socket);
template<class ExecutionContext> basic_datagram_socket(ExecutionContext& ctx, const protocol_type& protocol, const native_handle_type& native_socket);
basic_datagram_socket(basic_datagram_socket&& rhs);
template<class OtherProtocol, class OtherExecutor> basic_datagram_socket(basic_datagram_socket<OtherProtocol, OtherExecutor>&& rhs);
Update the basic_datagram_socket assignment operators [socket.dgram.assign] as follows:
basic_datagram_socket& operator=(basic_datagram_socket&& rhs);
template<class OtherProtocol, class OtherExecutor> basic_datagram_socket& operator=(basic_datagram_socket<OtherProtocol, OtherExecutor>&& rhs);
Update the class template basic_stream_socket [socket.stream] as follows:
namespace std { namespace experimental { namespace net { inline namespace v1 { template<class Protocol, class Executor> class basic_stream_socket : public basic_socket<Protocol, Executor> { public: // types: using executor_type = Executor; using native_handle_type = implementation-defined; // see [socket.reqmts.native] using protocol_type = Protocol; using endpoint_type = typename protocol_type::endpoint; template<class OtherExecutor> struct rebind_executor { using other = basic_stream_socket<Protocol, OtherExecutor>; }; // [socket.stream.cons], construct / copy / destroy: explicit basic_stream_socket(io_context& ctxconst executor_type& ex); template<class ExecutionContext> explicit basic_stream_socket(ExecutionContext& ctx); basic_stream_socket(io_context& ctxconst executor_type& ex, const protocol_type& protocol); template<class ExecutionContext> basic_stream_socket(ExecutionContext& ctx, const protocol_type& protocol); basic_stream_socket(io_context& ctxconst executor_type& ex, const endpoint_type& endpoint); template<class ExecutionContext> basic_stream_socket(ExecutionContext& ctx, const endpoint_type& endpoint); basic_stream_socket(io_context& ctxconst executor_type& ex, const protocol_type& protocol, const native_handle_type& native_socket); template<class ExecutionContext> basic_stream_socket(ExecutionContext& ctx, const protocol_type& protocol, const native_handle_type& native_socket); basic_stream_socket(const basic_stream_socket&) = delete; basic_stream_socket(basic_stream_socket&& rhs); template<class OtherProtocol, class OtherExecutor> basic_stream_socket(basic_stream_socket<OtherProtocol, OtherExecutor>&& rhs); ~basic_stream_socket(); basic_stream_socket& operator=(const basic_stream_socket&) = delete; basic_stream_socket& operator=(basic_stream_socket&& rhs); template<class OtherProtocol, class OtherExecutor> basic_stream_socket& operator=(basic_stream_socket<OtherProtocol, OtherExecutor>&& rhs);
[...]
Update the basic_stream_socket constructors [socket.stream.cons] as follows:
explicit basic_stream_socket(io_context& ctxconst executor_type& ex);
template<class ExecutionContext> explicit basic_stream_socket(ExecutionContext& ctx);
basic_stream_socket(io_context& ctxconst executor_type& ex, const protocol_type& protocol);
template<class ExecutionContext> explicit basic_stream_socket(ExecutionContext& ctx, const protocol_type& protocol);
basic_stream_socket(io_context& ctxconst executor_type& ex, const endpoint_type& endpoint);
template<class ExecutionContext> explicit basic_stream_socket(ExecutionContext& ctx, const endpoint_type& endpoint);
basic_stream_socket(io_context& ctxconst executor_type& ex, const protocol_type& protocol, const native_handle_type& native_socket);
template<class ExecutionContext> basic_stream_socket(ExecutionContext& ctx, const protocol_type& protocol, const native_handle_type& native_socket);
basic_stream_socket(basic_stream_socket&& rhs);
template<class OtherProtocol, class OtherExecutor> basic_stream_socket(basic_stream_socket<OtherProtocol, OtherExecutor>&& rhs);
Update the basic_stream_socket assignment operators [socket.stream.assign] as follows:
basic_stream_socket& operator=(basic_stream_socket&& rhs);
template<class OtherProtocol, class OtherExecutor> basic_stream_socket& operator=(basic_stream_socket<OtherProtocol, OtherExecutor>&& rhs);
Update the class template basic_socket_acceptor [socket.acceptor] as follows:
namespace std { namespace experimental { namespace net { inline namespace v1 { template<class AcceptableProtocol, class Executor> class basic_socket_acceptor : public socket_base { public: // types: using executor_type =io_context::executor_typeExecutor; using native_handle_type = implementation-defined; // see [socket.reqmts.native] using protocol_type = AcceptableProtocol; using endpoint_type = typename protocol_type::endpoint;using socket_type = typename protocol_type::socket;template<class OtherExecutor> struct rebind_executor { using other = basic_socket_acceptor<AcceptableProtocol, OtherExecutor>; }; // [socket.acceptor.cons], construct / copy / destroy: explicit basic_socket_acceptor(io_context& ctxconst executor_type& ex); template<class ExecutionContext> explicit basic_socket_acceptor(ExecutionContext& ctx); basic_socket_acceptor(io_context& ctxconst executor_type& ex, const protocol_type& protocol); template<class ExecutionContext> basic_socket_acceptor(ExecutionContext& ctx, const protocol_type& protocol); basic_socket_acceptor(io_context& ctxconst executor_type& ex, const endpoint_type& endpoint, bool reuse_addr = true); template<class ExecutionContext> basic_socket_acceptor(ExecutionContext& ctx, const endpoint_type& endpoint, bool reuse_addr = true); basic_socket_acceptor(io_context& ctxconst executor_type& ex, const protocol_type& protocol, const native_handle_type& native_socket); template<class ExecutionContext> basic_socket_acceptor(ExecutionContext& ctx, const protocol_type& protocol, const native_handle_type& native_socket); basic_socket_acceptor(const basic_socket_acceptor&) = delete; basic_socket_acceptor(basic_socket_acceptor&& rhs); template<class OtherProtocol, class OtherExecutor> basic_socket_acceptor(basic_socket_acceptor<OtherProtocol, OtherExecutor>&& rhs); ~basic_socket_acceptor(); basic_socket_acceptor& operator=(const basic_socket_acceptor&) = delete; basic_socket_acceptor& operator=(basic_socket_acceptor&& rhs); template<class OtherProtocol, class OtherExecutor> basic_socket_acceptor& operator=(basic_socket_acceptor<OtherProtocol, OtherExecutor>&& rhs); // [socket.acceptor.ops], basic_socket_acceptor operations: executor_type get_executor() noexcept; native_handle_type native_handle(); // see [socket.reqmts.native] void open(const protocol_type& protocol = protocol_type()); void open(const protocol_type& protocol, error_code& ec); void assign(const protocol_type& protocol, const native_handle_type& native_acceptor); // see [socket.reqmts.native] void assign(const protocol_type& protocol, const native_handle_type& native_acceptor, error_code& ec); // see [socket.reqmts.native] native_handle_type release(); // see [socket.reqmts.native] native_handle_type release(error_code& ec); // see [socket.reqmts.native] bool is_open() const noexcept; void close(); void close(error_code& ec); void cancel(); void cancel(error_code& ec); template<class SettableSocketOption> void set_option(const SettableSocketOption& option); template<class SettableSocketOption> void set_option(const SettableSocketOption& option, error_code& ec); template<class GettableSocketOption> void get_option(GettableSocketOption& option) const; template<class GettableSocketOption> void get_option(GettableSocketOption& option, error_code& ec) const; template<class IoControlCommand> void io_control(IoControlCommand& command); template<class IoControlCommand> void io_control(IoControlCommand& command, error_code& ec); void non_blocking(bool mode); void non_blocking(bool mode, error_code& ec); bool non_blocking() const; void native_non_blocking(bool mode); void native_non_blocking(bool mode, error_code& ec); bool native_non_blocking() const; void bind(const endpoint_type& endpoint); void bind(const endpoint_type& endpoint, error_code& ec); void listen(int backlog = max_listen_connections); void listen(int backlog, error_code& ec); endpoint_type local_endpoint() const; endpoint_type local_endpoint(error_code& ec) const; void enable_connection_aborted(bool mode); bool enable_connection_aborted() const;socket_typetypename Protocol::socket accept();socket_typetypename Protocol::socket accept(error_code& ec);socket_type accept(io_context& ctx);socket_type accept(io_context& ctx, error_code& ec);template<class OtherExecutor> typename Protocol::socket::template rebind_executor<OtherExecutor>::other accept(const OtherExecutor& ex); template<class OtherExecutor> typename Protocol::socket::template rebind_executor<OtherExecutor>::other accept(const OtherExecutor& ex, error_code& ec); template<class ExecutionContext> typename Protocol::socket::template rebind_executor<typename ExecutionContext::executor_type>::other accept(ExecutionContext& ctx); template<class ExecutionContext> typename Protocol::socket::template rebind_executor<typename ExecutionContext::executor_type>::other accept(ExecutionContext& ctx, error_code& ec); template<class CompletionToken> DEDUCED async_accept(CompletionToken&& token);template<class CompletionToken>DEDUCED async_accept(io_context& ctx, CompletionToken&& token);template<class OtherExecutor, class CompletionToken> DEDUCED async_accept(const OtherExecutor& ex, CompletionToken&& token); template<class ExecutionContext, class CompletionToken> DEDUCED async_accept(ExecutionContext& ctx, CompletionToken&& token);socket_typetypename Protocol::socket accept(endpoint_type& endpoint);socket_typetypename Protocol::socket accept(endpoint_type& endpoint, error_code& ec);socket_type accept(io_context& ctx, endpoint_type& endpoint);socket_type accept(io_context& ctx, endpoint_type& endpoint,error_code& ec);template<class OtherExecutor> typename Protocol::socket::template rebind_executor<OtherExecutor>::other accept(const OtherExecutor& ex, endpoint_type& endpoint); template<class OtherExecutor> typename Protocol::socket::template rebind_executor<OtherExecutor>::other accept(const OtherExecutor& ex, endpoint_type& endpoint, error_code& ec); template<class ExecutionContext> typename Protocol::socket::template rebind_executor<typename ExecutionContext::executor_type>::other accept(ExecutionContext& ctx, endpoint_type& endpoint); template<class ExecutionContext> typename Protocol::socket::template rebind_executor<typename ExecutionContext::executor_type>::other accept(ExecutionContext& ctx, endpoint_type& endpoint, error_code& ec); template<class CompletionToken> DEDUCED async_accept(endpoint_type& endpoint, CompletionToken&& token);template<class CompletionToken>DEDUCED async_accept(io_context& ctx, endpoint_type& endpoint,CompletionToken&& token);template<class OtherExecutor, class CompletionToken> DEDUCED async_accept(const OtherExecutor& ex, endpoint_type& endpoint, CompletionToken&& token); template<class ExecutionContext, class CompletionToken> DEDUCED async_accept(ExecutionContext& ctx, endpoint_type& endpoint, CompletionToken&& token);
Update the basic_socket_acceptor constructors [socket.stream.cons] as follows:
explicit basic_socket_acceptor(io_context& ctxconst executor_type& ex);
template<class ExecutionContext> explicit basic_socket_acceptor(ExecutionContext& ctx);
basic_socket_acceptor(io_context& ctxconst executor_type& ex, const protocol_type& protocol);
template<class ExecutionContext> explicit basic_socket_acceptor(ExecutionContext& ctx, const protocol_type& protocol);
basic_socket_acceptor(io_context& ctxconst executor_type& ex, const endpoint_type& endpoint, bool reuse_addr = true);
open(endpoint.protocol()); if (reuse_addr) set_option(reuse_address(true)); bind(endpoint); listen();
template<class ExecutionContext> explicit basic_socket_acceptor(ExecutionContext& ctx, const endpoint_type& endpoint, bool reuse_addr = true);
basic_socket_acceptor(io_context& ctxconst executor_type& ex, const protocol_type& protocol, const native_handle_type& native_acceptor);
template<class ExecutionContext> basic_socket_acceptor(ExecutionContext& ctx, const protocol_type& protocol, const native_handle_type& native_socket);
basic_socket_acceptor(basic_socket_acceptor&& rhs);
template<class OtherProtocol, class OtherExecutor> basic_socket_acceptor(basic_socket_acceptor<OtherProtocol, OtherExecutor>&& rhs);
Update the basic_socket_acceptor assignment operators [socket.stream.assign] as follows:
basic_socket_acceptor& operator=(basic_socket_acceptor&& rhs);
template<class OtherProtocol, class OtherExecutor> basic_socket_acceptor& operator=(basic_socket_acceptor<OtherProtocol, OtherExecutor>&& rhs);
Update the basic_socket_acceptor operations [socket.stream.ops] as follows:
[...]
socket_typetypename Protocol::socket accept();socket_typetypename Protocol::socket accept(error_code& ec);
socket_type accept(io_context& ctx);socket_type accept(io_context& ctx, error_code& ec);template<class OtherExecutor> typename Protocol::socket::template rebind_executor<OtherExecutor>::other accept(const OtherExecutor& ex); template<class OtherExecutor> typename Protocol::socket::template rebind_executor<OtherExecutor>::other accept(const OtherExecutor& ex, error_code& ec);
native_handle_type h = accept(native_handle(), nullptr, 0);
template<class ExecutionContext> typename Protocol::socket::template rebind_executor<typename ExecutionContext::executor_type>::other accept(ExecutionContext& ctx); template<class ExecutionContext> typename Protocol::socket::template rebind_executor<typename ExecutionContext::executor_type>::other accept(ExecutionContext& ctx, error_code& ec);
template<class CompletionToken> DEDUCED async_accept(CompletionToken&& token);
template<class CompletionToken>DEDUCED async_accept(io_context& ctx, CompletionToken&& token);template<class OtherExecutor, class CompletionToken> DEDUCED async_accept(const OtherExecutor& ex, CompletionToken&& token);
native_handle_type h = accept(native_handle(), nullptr, 0);
template<class ExecutionContext, class CompletionToken> DEDUCED async_accept(ExecutionContext& ctx, CompletionToken&& token);
async_accept(ctx.get_executor(), forward<CompletionToken>(token))
socket_typetypename Protocol::socket accept(endpoint_type& endpoint);socket_typetypename Protocol::socket accept(endpoint_type& endpoint, error_code& ec);
socket_type accept(io_context& ctx, endpoint_type& endpoint);socket_type accept(io_context& ctx, endpoint_type& endpoint,error_code& ec);template<class OtherExecutor> typename Protocol::socket::template rebind_executor<OtherExecutor>::other accept(const OtherExecutor& ex, endpoint_type& endpoint); template<class OtherExecutor> typename Protocol::socket::template rebind_executor<OtherExecutor>::other accept(const OtherExecutor& ex, endpoint_type& endpoint, error_code& ec);
socklen_t endpoint_len = endpoint.capacity(); native_handle_type h = accept(native_handle(), endpoint.data(), &endpoint_len); if (h >= 0) endpoint.resize(endpoint_len);
template<class ExecutionContext> typename Protocol::socket::template rebind_executor<typename ExecutionContext::executor_type>::other accept(ExecutionContext& ctx, endpoint_type& endpoint); template<class ExecutionContext> typename Protocol::socket::template rebind_executor<typename ExecutionContext::executor_type>::other accept(ExecutionContext& ctx, endpoint_type& endpoint, error_code& ec);
template<class CompletionToken> DEDUCED async_accept(endpoint_type& endpoint, CompletionToken&& token);
template<class CompletionToken>DEDUCED async_accept(io_context& ctx, endpoint_type& endpoint,CompletionToken&& token);template<class OtherExecutor, class CompletionToken> DEDUCED async_accept(const OtherExecutor& ex, endpoint_type& endpoint, CompletionToken&& token);
socklen_t endpoint_len = endpoint.capacity(); native_handle_type h = accept(native_handle(), endpoint.data(), &endpoint_len); if (h >= 0) endpoint.resize(endpoint_len);
template<class ExecutionContext, class CompletionToken> DEDUCED async_accept(ExecutionContext& ctx, endpoint_type& endpoint, CompletionToken&& token);
async_accept(ctx.get_executor(), forward<CompletionToken>(token))
Update the class template basic_socket_streambuf [socket.streambuf] as follows:
namespace std { namespace experimental { namespace net { inline namespace v1 { template<class Protocol, class Clock, class WaitTraits, class Executor = executor> class basic_socket_streambuf : public basic_streambuf<char> { public: // types: using executor_type = Executor; using protocol_type = Protocol; using endpoint_type = typename protocol_type::endpoint; using clock_type = Clock; using time_point = typename clock_type::time_point; using duration = typename clock_type::duration; using wait_traits_type = WaitTraits; // [socket.streambuf.cons], construct / copy / destroy: basic_socket_streambuf(); explicit basic_socket_streambuf(basic_stream_socket<protocol_type, executor_type> s); basic_socket_streambuf(const basic_socket_streambuf&) = delete; basic_socket_streambuf(basic_socket_streambuf&& rhs); virtual ~basic_socket_streambuf(); basic_socket_streambuf& operator=(const basic_socket_streambuf&) = delete; basic_socket_streambuf& operator=(basic_socket_streambuf&& rhs); // [socket.streambuf.members], members: basic_socket_streambuf* connect(const endpoint_type& e); template<class... Args> basic_socket_streambuf* connect(Args&&... ); basic_socket_streambuf* close(); basic_socket<protocol_type, executor_type>& socket(); error_code error() const; time_point expiry() const; void expires_at(const time_point& t); void expires_after(const duration& d); protected: // overridden virtual functions: virtual int_type underflow() override; virtual int_type pbackfail(int_type c = traits_type::eof()) override; virtual int_type overflow(int_type c = traits_type::eof()) override; virtual int sync() override; virtual streambuf* setbuf(char_type* s, streamsize n) override; private: basic_stream_socket<protocol_type, executor_type> socket_; // exposition only error_code ec_; // exposition only time_point expiry_; // exposition only }; } // inline namespace v1 } // namespace net } // namespace experimental } // namespace std
basic_socket_streambuf();
explicit basic_socket_streambuf(basic_stream_socket<protocol_type, executor_type> s);
basic_socket_streambuf(basic_socket_streambuf&& rhs);
virtual ~basic_socket_streambuf();
basic_socket_streambuf& operator=(basic_socket_streambuf&& rhs);
basic_socket_streambuf* connect(const endpoint_type& e);
template<class... Args> basic_socket_streambuf* connect(Args&&... args);
basic_socket_streambuf* close();
basic_socket<protocol_type, executor_type>& socket();
Update the class template basic_socket_iostream [socket.iostream] as follows:
namespace std { namespace experimental { namespace net { inline namespace v1 { template<class Protocol, class Clock, class WaitTraits, class Executor = executor> class basic_socket_iostream : public basic_iostream<char> { public: // types: using executor_type = Executor; using protocol_type = Protocol; using endpoint_type = typename protocol_type::endpoint; using clock_type = Clock; using time_point = typename clock_type::time_point; using duration = typename clock_type::duration; using wait_traits_type = WaitTraits; // [socket.iostream.cons], construct / copy / destroy: basic_socket_iostream(); explicit basic_socket_iostream(basic_stream_socket<protocol_type, executor_type> s); basic_socket_iostream(const basic_socket_iostream&) = delete; basic_socket_iostream(basic_socket_iostream&& rhs); template<class... Args> explicit basic_socket_iostream(Args&&... args); basic_socket_iostream& operator=(const basic_socket_iostream&) = delete; basic_socket_iostream& operator=(basic_socket_iostream&& rhs); // [socket.iostream.members], members: template<class... Args> void connect(Args&&... args); void close(); basic_socket_streambuf<protocol_type, clock_type, wait_traits_type, executor_type>* rdbuf() const; basic_socket<protocol_type, executor_type>& socket(); error_code error() const; time_point expiry() const; void expires_at(const time_point& t); void expires_after(const duration& d); private: basic_socket_streambuf<protocol_type, clock_type, wait_traits_type, executor_type> sb_; // exposition only }; } // inline namespace v1 } // namespace net } // namespace experimental } // namespace std
basic_socket_iostream();
explicit basic_socket_iostream(basic_stream_socket<protocol_type, executor_type> s);
basic_socket_iostream(basic_socket_iostream&& rhs);
template<class... Args> explicit basic_socket_iostream(Args&&... args);
basic_socket_iostream& operator=(basic_socket_iostream&& rhs);
template<class... Args> void connect(Args&&... args);
void close();
basic_socket_streambuf<protocol_type, clock_type, wait_traits_type, executor_type>* rdbuf() const;
basic_socket<protocol_type, executor_type>& socket();
Update the connect function [socket.algo.connect] as follows:
template<class Protocol, class Executor, class EndpointSequence> typename Protocol::endpoint connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints); template<class Protocol, class Executor, class EndpointSequence> typename Protocol::endpoint connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints, error_code& ec);
template<class Protocol, class Executor, class EndpointSequence, class ConnectCondition> typename Protocol::endpoint connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints, ConnectCondition c); template<class Protocol, class Executor, class EndpointSequence, class ConnectCondition> typename Protocol::endpoint connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints, ConnectCondition c, error_code& ec);
template<class Protocol, class Executor, class InputIterator> InputIterator connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last); template<class Protocol, class Executor, class InputIterator> InputIterator connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last, error_code& ec);
template<class Protocol, class Executor, class InputIterator, class ConnectCondition> InputIterator connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last, ConnectCondition c); template<class Protocol, class Executor, class InputIterator, class ConnectCondition> InputIterator connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last, ConnectCondition c, error_code& ec);
Update the async_connect function [socket.algo.async.connect] as follows:
template<class Protocol, class Executor, class EndpointSequence, class CompletionToken> DEDUCED async_connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints, CompletionToken&& token);
async_connect(s, endpoints, [](auto, auto){ return true; }, forward<CompletionToken>(token))
template<class Protocol, class Executor, class EndpointSequence, class ConnectCondition, class CompletionToken> DEDUCED async_connect(basic_socket<Protocol, Executor>& s, const EndpointSequence& endpoints, ConnectCondition c, CompletionToken&& token);
template<class Protocol, class Executor, class InputIterator, class CompletionToken> DEDUCED async_connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last, CompletionToken&& token);
async_connect(s, first, last, [](auto, auto){ return true; }, forward<CompletionToken>(token))
template<class Protocol, class Executor, class InputIterator, class ConnectCondition, class CompletionToken> DEDUCED async_connect(basic_socket<Protocol, Executor>& s, InputIterator first, InputIterator last, ConnectCondition c, CompletionToken&& token);
Update the <experimental/internet> synopsis [internet.synop] as follows:
[...]
template<class InternetProtocol, class Executor = executor> class basic_resolver;
Update the class template basic_resolver [internet.resolver] as follows:
namespace std { namespace experimental { namespace net { inline namespace v1 { namespace ip { template<class InternetProtocol, class Executor = executor> class basic_resolver : public resolver_base { public: // types: using executor_type =io_context::executor_typeExecutor; using protocol_type = InternetProtocol; using endpoint_type = typename InternetProtocol::endpoint; using results_type = basic_resolver_results<InternetProtocol>; template<class OtherExecutor> struct rebind_executor { using other = basic_resolver<InternetProtocol, OtherExecutor>; }; // [internet.resolver.cons], construct / copy / destroy: explicit basic_resolver(io_context& ctxconst executor_type& ex); template<class ExecutionContext> explicit basic_resolver(ExecutionContext& ctx); basic_resolver(const basic_resolver&) = delete; basic_resolver(basic_resolver&& rhs) noexcept;
Update the basic_resolver constructors [internet.resolver.cons] as follows:
explicit basic_resolver(io_context& ctxconst executor_type& ex);
template<class ExecutionContext> explicit basic_resolver(ExecutionContext& ctx);
basic_resolver(basic_resolver&& rhs) noexcept;
Changes in R1: