Sockets template for berkeley's api and substitution for winsock2
This commit is contained in:
parent
f62215635b
commit
36f3c7a8ec
@ -19,8 +19,11 @@ set(CMAKE_CXX_MODULE_STD 1)
|
|||||||
|
|
||||||
add_subdirectory(modules/exceptions)
|
add_subdirectory(modules/exceptions)
|
||||||
add_subdirectory(modules/threads)
|
add_subdirectory(modules/threads)
|
||||||
|
add_subdirectory(modules/streams)
|
||||||
|
add_subdirectory(modules/sockets)
|
||||||
|
|
||||||
add_subdirectory(modules/asyncio)
|
add_subdirectory(modules/asyncio)
|
||||||
add_subdirectory(programs/lab4)
|
add_subdirectory(programs/lab4)
|
||||||
|
|
||||||
add_executable(main main.cpp)
|
add_executable(main main.cpp)
|
||||||
target_link_libraries(main PRIVATE exceptions threads)
|
target_link_libraries(main PRIVATE exceptions threads sockets streams)
|
||||||
40
main.cpp
40
main.cpp
@ -1,18 +1,32 @@
|
|||||||
#include <iostream>
|
import std;
|
||||||
|
|
||||||
import ru.landgrafhomyak.BGTU.networks_1.exceptions;
|
|
||||||
import ru.landgrafhomyak.BGTU.networks_1.threads;
|
import ru.landgrafhomyak.BGTU.networks_1.threads;
|
||||||
|
import ru.landgrafhomyak.BGTU.networks_1.sockets;
|
||||||
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
try {
|
LdH::Sockets::init_sockets();
|
||||||
auto t = LdH::fork("123", []() { std::cout << "123" << std::endl; });
|
|
||||||
t.join();
|
auto server = LdH::Sockets::listen_tcp(LdH::Sockets::IPv4Address::parse("127.0.0.1"), 8081, 1);
|
||||||
t.destroy();
|
|
||||||
} catch (LdH::Exception const &e) {
|
auto server_thread = LdH::fork("server", [&] {
|
||||||
std::cerr << "Uncaught exception in main:\n";
|
auto server_stream = server.wait_for_connection();
|
||||||
e.printStackTrace();
|
char buffer[10];
|
||||||
} catch (std::exception const &e) {
|
server_stream.sock.read(10, buffer);
|
||||||
std::cerr << "Uncaught exception in main:\n" << e.what() << "\n";
|
std::cout << buffer << std::endl;
|
||||||
}
|
server_stream.sock.close();
|
||||||
|
});
|
||||||
|
auto client_thread = LdH::fork("client", [&] {
|
||||||
|
auto client = LdH::Sockets::connect_tcp(LdH::Sockets::IPv4Address::parse("127.0.0.1"), 8081);
|
||||||
|
char buffer[10] = "hello\n";
|
||||||
|
client.write(10, buffer);
|
||||||
|
client.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
server_thread.join();
|
||||||
|
client_thread.join();
|
||||||
|
server_thread.destroy();
|
||||||
|
client_thread.destroy();
|
||||||
|
server.close();
|
||||||
|
|
||||||
|
LdH::Sockets::deinit_sockets();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
export module ru.landgrafhomyak.BGTU.networks_1.exceptions.windows;
|
export module ru.landgrafhomyak.BGTU.networks_1.exceptions.windows;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
import std;
|
import std;
|
||||||
import ru.landgrafhomyak.BGTU.networks_1.exceptions;
|
import ru.landgrafhomyak.BGTU.networks_1.exceptions;
|
||||||
|
|
||||||
@ -49,3 +54,5 @@ void LdH::throwFromLastWindowsErrOrTimeout() {
|
|||||||
return;
|
return;
|
||||||
LdH::throwFromWindowsErrCode(err);
|
LdH::throwFromWindowsErrCode(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
13
modules/sockets/CMakeLists.txt
Normal file
13
modules/sockets/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
add_library(sockets STATIC)
|
||||||
|
|
||||||
|
target_sources(
|
||||||
|
sockets
|
||||||
|
|
||||||
|
PUBLIC
|
||||||
|
FILE_SET cxx_modules TYPE CXX_MODULES FILES
|
||||||
|
src/common.cppm
|
||||||
|
src/berkeley_sockets.cppm
|
||||||
|
src/windows_binds.cppm
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(sockets PRIVATE wsock32 ws2_32 exceptions PUBLIC streams)
|
||||||
460
modules/sockets/src/berkeley_sockets.cppm
Normal file
460
modules/sockets/src/berkeley_sockets.cppm
Normal file
@ -0,0 +1,460 @@
|
|||||||
|
export module ru.landgrafhomyak.BGTU.networks_1.sockets:berkeley_sockets;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import ru.landgrafhomyak.BGTU.networks_1.exceptions;
|
||||||
|
import ru.landgrafhomyak.BGTU.networks_1.streams;
|
||||||
|
|
||||||
|
|
||||||
|
namespace LdH::Sockets::Berkeley {
|
||||||
|
export
|
||||||
|
struct sized_ptr {
|
||||||
|
std::size_t size;
|
||||||
|
void *ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class addr_t, class af_t>
|
||||||
|
concept _raw_addr = std::default_initializable<addr_t> && std::destructible<addr_t> &&
|
||||||
|
std::copyable<addr_t> && std::movable<addr_t> &&
|
||||||
|
requires { { addr_t::any() } -> std::same_as<addr_t>; } &&
|
||||||
|
requires(char const *raw) { { addr_t::parse(raw) } -> std::same_as<addr_t>; } &&
|
||||||
|
requires(addr_t self) { { self.raw_ptr() } -> std::same_as<sized_ptr>; } &&
|
||||||
|
requires { { addr_t::af_value() } -> std::same_as<af_t>; };
|
||||||
|
|
||||||
|
|
||||||
|
export
|
||||||
|
template<class ctx_t>
|
||||||
|
concept BerkeleySocketsContext = requires { typename ctx_t::addr; } &&
|
||||||
|
requires { typename ctx_t::af_t; } &&
|
||||||
|
requires { typename ctx_t::af; } &&
|
||||||
|
requires { { ctx_t::af::inet() } -> std::same_as<typename ctx_t::af_t>; } &&
|
||||||
|
requires { { ctx_t::af::inet6() } -> std::same_as<typename ctx_t::af_t>; } &&
|
||||||
|
requires { { ctx_t::af::ipx() } -> std::same_as<typename ctx_t::af_t>; } &&
|
||||||
|
requires { typename ctx_t::sock_type_t; } &&
|
||||||
|
requires { typename ctx_t::sock_type; } &&
|
||||||
|
requires { { ctx_t::sock_type::stream() } -> std::same_as<typename ctx_t::sock_type_t>; } &&
|
||||||
|
requires { { ctx_t::sock_type::dgram() } -> std::same_as<typename ctx_t::sock_type_t>; } &&
|
||||||
|
requires { typename ctx_t::proto_t; } &&
|
||||||
|
requires { typename ctx_t::proto; } &&
|
||||||
|
requires { { ctx_t::proto::tcp() } -> std::same_as<typename ctx_t::proto_t>; } &&
|
||||||
|
requires { { ctx_t::proto::udp() } -> std::same_as<typename ctx_t::proto_t>; } &&
|
||||||
|
requires { { ctx_t::proto::icmp() } -> std::same_as<typename ctx_t::proto_t>; } &&
|
||||||
|
requires { typename ctx_t::addr::ipv4; requires _raw_addr<typename ctx_t::addr::ipv4, typename ctx_t::af_t>; } &&
|
||||||
|
requires { typename ctx_t::addr::ipv6; requires _raw_addr<typename ctx_t::addr::ipv6, typename ctx_t::af_t>; } &&
|
||||||
|
requires { typename ctx_t::socket_t; } &&
|
||||||
|
std::default_initializable<typename ctx_t::socket_t> && std::movable<typename ctx_t::socket_t> && std::destructible<typename ctx_t::socket_t> &&
|
||||||
|
requires(typename ctx_t::af_t a, typename ctx_t::sock_type_t t, typename ctx_t::proto_t p) { { ctx_t::socket_t::create(a, t, p) } -> std::same_as<typename ctx_t::socket_t>; } &&
|
||||||
|
requires(typename ctx_t::socket_t s, std::size_t q) { { s.listen(q) } -> std::same_as<void>; } &&
|
||||||
|
requires(typename ctx_t::socket_t s, typename ctx_t::addr::ipv4 a, std::uint_least16_t p) { { s.connect(a, p) } -> std::same_as<void>; } &&
|
||||||
|
requires(typename ctx_t::socket_t s, typename ctx_t::addr::ipv6 a, std::uint_least16_t p) { { s.connect(a, p) } -> std::same_as<void>; } &&
|
||||||
|
requires(typename ctx_t::socket_t s, typename ctx_t::addr::ipv4 a, std::uint_least16_t p) { { s.bind(a, p) } -> std::same_as<void>; } &&
|
||||||
|
requires(typename ctx_t::socket_t s, typename ctx_t::addr::ipv6 a, std::uint_least16_t p) { { s.bind(a, p) } -> std::same_as<void>; } &&
|
||||||
|
requires(typename ctx_t::socket_t s) { { s.close() } -> std::same_as<void>; } &&
|
||||||
|
requires(typename ctx_t::socket_t s, std::size_t c, char const *d) { { s.send_stream(c, d) } -> std::same_as<void>; } &&
|
||||||
|
requires(typename ctx_t::socket_t s, std::size_t c, char *d) { { s.recv_stream(c, d) } -> std::same_as<void>; } &&
|
||||||
|
requires(typename ctx_t::socket_t s, typename ctx_t::addr::ipv4 *a) { { s.accept(a) } -> std::same_as<typename ctx_t::socket_t>; } &&
|
||||||
|
requires(typename ctx_t::socket_t s, typename ctx_t::addr::ipv6 *a) { { s.accept(a) } -> std::same_as<typename ctx_t::socket_t>; };
|
||||||
|
|
||||||
|
export
|
||||||
|
template<class ctx_t> requires BerkeleySocketsContext<ctx_t>
|
||||||
|
class Address;
|
||||||
|
|
||||||
|
template<class addr_t>
|
||||||
|
class _addr_internals {
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class raw_t>
|
||||||
|
class _addr_wrapper {
|
||||||
|
private:
|
||||||
|
bool _has_value;
|
||||||
|
raw_t _value;
|
||||||
|
|
||||||
|
_addr_wrapper(bool has_value, raw_t value) : _has_value{has_value}, _value{value} {
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ctx_t> requires BerkeleySocketsContext<ctx_t>
|
||||||
|
friend
|
||||||
|
class Address;
|
||||||
|
|
||||||
|
template<class addr_t>
|
||||||
|
friend
|
||||||
|
class _addr_internals;
|
||||||
|
|
||||||
|
public:
|
||||||
|
_addr_wrapper() : _has_value{false}, _value{} {
|
||||||
|
}
|
||||||
|
|
||||||
|
_addr_wrapper(_addr_wrapper const &other) noexcept = default;
|
||||||
|
|
||||||
|
_addr_wrapper(_addr_wrapper &&other) noexcept : _has_value{other._has_value}, _value{other._value} {
|
||||||
|
other._has_value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_addr_wrapper &operator=(_addr_wrapper const &other) = default;
|
||||||
|
|
||||||
|
_addr_wrapper &operator=(_addr_wrapper &&other) noexcept {
|
||||||
|
this->_has_value = other._has_value;
|
||||||
|
other._has_value = false;
|
||||||
|
this->_value = std::move(other._value);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~_addr_wrapper() noexcept = default;
|
||||||
|
|
||||||
|
static _addr_wrapper parse(char const *raw) {
|
||||||
|
return {true, raw_t::parse(raw)};
|
||||||
|
}
|
||||||
|
|
||||||
|
static _addr_wrapper parse(std::string const &raw) {
|
||||||
|
return {true, raw_t::parse(raw.c_str())};
|
||||||
|
}
|
||||||
|
|
||||||
|
static _addr_wrapper any() {
|
||||||
|
return _addr_wrapper{raw_t::any()};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export
|
||||||
|
template<class ctx_t> requires BerkeleySocketsContext<ctx_t>
|
||||||
|
using IPv4Address = _addr_wrapper<typename ctx_t::addr::ipv4>;
|
||||||
|
|
||||||
|
export
|
||||||
|
template<class ctx_t> requires BerkeleySocketsContext<ctx_t>
|
||||||
|
using IPv6Address = _addr_wrapper<typename ctx_t::addr::ipv6>;
|
||||||
|
|
||||||
|
|
||||||
|
template<class _raw_addr_t>
|
||||||
|
class _addr_internals<_addr_wrapper<_raw_addr_t> > {
|
||||||
|
public:
|
||||||
|
using raw_t = _raw_addr_t;
|
||||||
|
|
||||||
|
static _addr_wrapper<_raw_addr_t> empty() {
|
||||||
|
return _addr_wrapper<_raw_addr_t>{false, _raw_addr_t{}};
|
||||||
|
}
|
||||||
|
|
||||||
|
static _addr_wrapper<_raw_addr_t> wrap(_raw_addr_t value) {
|
||||||
|
return _addr_wrapper<_raw_addr_t>{true, value};
|
||||||
|
}
|
||||||
|
|
||||||
|
static _raw_addr_t raw_of(_addr_wrapper<_raw_addr_t> const &value) {
|
||||||
|
return value._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool has_value(_addr_wrapper<_raw_addr_t> const &value) {
|
||||||
|
return value._has_value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class ctx_t> requires BerkeleySocketsContext<ctx_t>
|
||||||
|
class Address {
|
||||||
|
private:
|
||||||
|
enum _type_t {
|
||||||
|
_type_EMPTY,
|
||||||
|
_type_IPv4,
|
||||||
|
_type_IPv6,
|
||||||
|
};
|
||||||
|
|
||||||
|
_type_t _type;
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
} empty;
|
||||||
|
|
||||||
|
ctx_t::ipv4 ipv4;
|
||||||
|
ctx_t::ipv6 ipv6;
|
||||||
|
} _value;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Address() : _type{_type_EMPTY}, _value{.empty{}} {
|
||||||
|
}
|
||||||
|
|
||||||
|
Address(IPv4Address<ctx_t> const &value) : _type{_type_IPv4}, _value{.ipv4 = value._value} {
|
||||||
|
}
|
||||||
|
|
||||||
|
Address(IPv6Address<ctx_t> const &value) : _type{_type_IPv6}, _value{.ipv6 = value._value} {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
enum _socket_state_t {
|
||||||
|
_socket_state_UNINITIALIZED,
|
||||||
|
_socket_state_MOVED,
|
||||||
|
_socket_state_AVAILABLE,
|
||||||
|
_socket_state_READING,
|
||||||
|
_socket_state_WRITING,
|
||||||
|
_socket_state_BOTH,
|
||||||
|
_socket_state_CLOSED,
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class ctx_t> requires BerkeleySocketsContext<ctx_t>
|
||||||
|
class _berkeley_socket_commons {
|
||||||
|
private:
|
||||||
|
std::atomic<_socket_state_t> _state;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ctx_t::socket_t _value;
|
||||||
|
|
||||||
|
explicit _berkeley_socket_commons(ctx_t::socket_t &&value) : _state{_socket_state_AVAILABLE}, _value{std::move(value)} {
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
_berkeley_socket_commons() : _state{_socket_state_UNINITIALIZED}, _value{} {
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit _berkeley_socket_commons(_berkeley_socket_commons &&other) noexcept : _state{other._state.load()}, _value{std::move(other._value)} {
|
||||||
|
other._state.store(_socket_state_MOVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
_berkeley_socket_commons &operator=(_berkeley_socket_commons &&other) noexcept {
|
||||||
|
_socket_state_t current = this->_state.load();
|
||||||
|
while (true) {
|
||||||
|
switch (current) {
|
||||||
|
case _socket_state_UNINITIALIZED:
|
||||||
|
if (this->_state.compare_exchange_weak(current, _socket_state_AVAILABLE))
|
||||||
|
return current;
|
||||||
|
continue;
|
||||||
|
case _socket_state_MOVED:
|
||||||
|
if (this->_state.compare_exchange_weak(current, _socket_state_AVAILABLE))
|
||||||
|
return current;
|
||||||
|
continue;
|
||||||
|
case _socket_state_CLOSED:
|
||||||
|
if (this->_state.compare_exchange_weak(current, _socket_state_AVAILABLE))
|
||||||
|
return current;
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
LdH::abort("Variable already initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->_state.store(other._state.exchange(_socket_state_MOVED));
|
||||||
|
this->_value = std::move(other._value);
|
||||||
|
|
||||||
|
return *this;;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
_socket_state_t _cas_state(_socket_state_t expected, _socket_state_t next) {
|
||||||
|
this->_state.compare_exchange_strong(expected, next);
|
||||||
|
return expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<_socket_state_t exp1, _socket_state_t next1, _socket_state_t exp2, _socket_state_t next2>
|
||||||
|
_socket_state_t _cas_state_2() {
|
||||||
|
_socket_state_t current = this->_state.load();
|
||||||
|
while (true) {
|
||||||
|
switch (current) {
|
||||||
|
case exp1:
|
||||||
|
if (this->_state.compare_exchange_weak(current, next1))
|
||||||
|
return current;
|
||||||
|
continue;
|
||||||
|
case exp2:
|
||||||
|
if (this->_state.compare_exchange_weak(current, next2))
|
||||||
|
return current;
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void _start_reading(char const *busy_msg) {
|
||||||
|
switch (this->_cas_state_2<_socket_state_AVAILABLE, _socket_state_READING, _socket_state_WRITING, _socket_state_BOTH>()) {
|
||||||
|
case _socket_state_UNINITIALIZED:
|
||||||
|
LdH::abort("Socket not initialized");
|
||||||
|
case _socket_state_MOVED:
|
||||||
|
LdH::abort("Socket was moved to another location");
|
||||||
|
case _socket_state_AVAILABLE:
|
||||||
|
case _socket_state_WRITING:
|
||||||
|
break;
|
||||||
|
case _socket_state_READING:
|
||||||
|
case _socket_state_BOTH:
|
||||||
|
LdH::abort(busy_msg);
|
||||||
|
case _socket_state_CLOSED:
|
||||||
|
LdH::abort("Socket was closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _finish_reading() {
|
||||||
|
this->_cas_state_2<_socket_state_READING, _socket_state_AVAILABLE, _socket_state_BOTH, _socket_state_WRITING>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _start_writing(char const *busy_msg) {
|
||||||
|
switch (this->_cas_state_2<_socket_state_AVAILABLE, _socket_state_WRITING, _socket_state_READING, _socket_state_BOTH>()) {
|
||||||
|
case _socket_state_UNINITIALIZED:
|
||||||
|
LdH::abort("Socket not initialized");
|
||||||
|
case _socket_state_MOVED:
|
||||||
|
LdH::abort("Socket was moved to another location");
|
||||||
|
case _socket_state_AVAILABLE:
|
||||||
|
case _socket_state_READING:
|
||||||
|
break;
|
||||||
|
case _socket_state_WRITING:
|
||||||
|
case _socket_state_BOTH:
|
||||||
|
LdH::abort(busy_msg);
|
||||||
|
case _socket_state_CLOSED:
|
||||||
|
LdH::abort("Socket was closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _finish_writing() {
|
||||||
|
this->_cas_state_2<_socket_state_WRITING, _socket_state_AVAILABLE, _socket_state_BOTH, _socket_state_READING>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _close(char const *busy_msg) {
|
||||||
|
switch (this->_cas_state(_socket_state_AVAILABLE, _socket_state_CLOSED)) {
|
||||||
|
case _socket_state_UNINITIALIZED:
|
||||||
|
LdH::abort("Socket not initialized");
|
||||||
|
case _socket_state_MOVED:
|
||||||
|
LdH::abort("Socket was moved to another location");
|
||||||
|
case _socket_state_AVAILABLE:
|
||||||
|
break;
|
||||||
|
case _socket_state_READING:
|
||||||
|
case _socket_state_WRITING:
|
||||||
|
case _socket_state_BOTH:
|
||||||
|
LdH::abort(busy_msg);
|
||||||
|
case _socket_state_CLOSED:
|
||||||
|
LdH::abort("Socket already was closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->_value.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export
|
||||||
|
template<class ctx_t, class> requires BerkeleySocketsContext<ctx_t>
|
||||||
|
class StreamSocketsServer;
|
||||||
|
|
||||||
|
template<class sock_t>
|
||||||
|
class _socket_internals {
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export
|
||||||
|
template<class ctx_t> requires BerkeleySocketsContext<ctx_t>
|
||||||
|
class StreamSocket : public _berkeley_socket_commons<ctx_t>, public virtual LdH::Streams::InputStream, public virtual LdH::Streams::OutputStream {
|
||||||
|
private:
|
||||||
|
explicit StreamSocket(ctx_t::socket_t &&value) : _berkeley_socket_commons<ctx_t>{std::move(value)} {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<class ctx_t2, class> requires BerkeleySocketsContext<ctx_t2>
|
||||||
|
friend
|
||||||
|
class StreamSocketsServer;
|
||||||
|
|
||||||
|
|
||||||
|
template<class>
|
||||||
|
friend
|
||||||
|
class _socket_internals;
|
||||||
|
|
||||||
|
public:
|
||||||
|
StreamSocket() = default;
|
||||||
|
|
||||||
|
explicit StreamSocket(StreamSocket &&other) noexcept = default;
|
||||||
|
|
||||||
|
StreamSocket &operator=(StreamSocket &&other) noexcept = default;
|
||||||
|
|
||||||
|
void read(std::size_t size, char *data) override {
|
||||||
|
this->_start_reading("Socket already read by another thread");
|
||||||
|
this->_value.recv_stream(size, data);
|
||||||
|
this->_finish_reading();
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(std::size_t size, char const *data) override {
|
||||||
|
this->_start_writing("Socket already written by another thread");
|
||||||
|
this->_value.send_stream(size, data);
|
||||||
|
this->_finish_writing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() override {
|
||||||
|
this->_close("Socket is busy with reading or writing");
|
||||||
|
}
|
||||||
|
|
||||||
|
~StreamSocket() noexcept override = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<class ctx_t>
|
||||||
|
class _socket_internals<StreamSocket<ctx_t> > {
|
||||||
|
public:
|
||||||
|
static StreamSocket<ctx_t> wrap(ctx_t::socket_t &&raw) {
|
||||||
|
return StreamSocket<ctx_t>{std::move(raw)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export
|
||||||
|
template<class addr_t, class sock_t>
|
||||||
|
class SocketWithAddress {
|
||||||
|
public:
|
||||||
|
addr_t addr;
|
||||||
|
sock_t sock;
|
||||||
|
|
||||||
|
SocketWithAddress(addr_t addr, sock_t &&sock) : addr{addr}, sock{std::move(sock)} {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class ctx_t, class addr_t> requires BerkeleySocketsContext<ctx_t>
|
||||||
|
class StreamSocketsServer : public _berkeley_socket_commons<ctx_t> {
|
||||||
|
private:
|
||||||
|
explicit StreamSocketsServer(ctx_t::socket_t &&value) : _berkeley_socket_commons<ctx_t>{std::move(value)} {
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class>
|
||||||
|
friend
|
||||||
|
class _socket_internals;
|
||||||
|
|
||||||
|
public:
|
||||||
|
StreamSocketsServer() = default;
|
||||||
|
|
||||||
|
explicit StreamSocketsServer(StreamSocketsServer &&other) noexcept = default;
|
||||||
|
|
||||||
|
StreamSocketsServer &operator=(StreamSocketsServer &&other) noexcept = default;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
SocketWithAddress<addr_t, StreamSocket<ctx_t> > wait_for_connection() {
|
||||||
|
this->_start_reading("Socket already waiting for connections");
|
||||||
|
|
||||||
|
typename _addr_internals<addr_t>::raw_t addr{};
|
||||||
|
typename ctx_t::socket_t raw = this->_value.accept(&addr);
|
||||||
|
this->_finish_reading();
|
||||||
|
return SocketWithAddress<addr_t, StreamSocket<ctx_t> >{_addr_internals<addr_t>::wrap(addr), StreamSocket<ctx_t>{std::move(raw)}};
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
this->_close("Socket busy with waiting for connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
~StreamSocketsServer() noexcept = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<class ctx_t, class addr_t>
|
||||||
|
class _socket_internals<StreamSocketsServer<ctx_t, addr_t> > {
|
||||||
|
public:
|
||||||
|
static StreamSocketsServer<ctx_t, addr_t> wrap(ctx_t::socket_t &&raw) {
|
||||||
|
return StreamSocketsServer<ctx_t, addr_t>{std::move(raw)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export
|
||||||
|
template<class ctx_t, class addr_t> requires BerkeleySocketsContext<ctx_t>
|
||||||
|
[[nodiscard]]
|
||||||
|
StreamSocket<ctx_t> connect_tcp(addr_t addr, std::uint_least16_t port) {
|
||||||
|
if (!_addr_internals<addr_t>::has_value(addr))
|
||||||
|
LdH::abort("Address not initialized");
|
||||||
|
typename ctx_t::socket_t sock = ctx_t::socket_t::create(_addr_internals<addr_t>::raw_t::af_value(), ctx_t::sock_type::stream(), ctx_t::proto::tcp());
|
||||||
|
sock.connect(_addr_internals<addr_t>::raw_of(addr), port);
|
||||||
|
return _socket_internals<StreamSocket<ctx_t> >::wrap(std::move(sock));
|
||||||
|
}
|
||||||
|
|
||||||
|
export
|
||||||
|
template<class ctx_t, class addr_t> requires BerkeleySocketsContext<ctx_t>
|
||||||
|
[[nodiscard]]
|
||||||
|
StreamSocketsServer<ctx_t, addr_t> listen_tcp(addr_t addr, std::uint_least16_t port, std::size_t queue_size) {
|
||||||
|
if (!_addr_internals<addr_t>::has_value(addr))
|
||||||
|
LdH::abort("Address not initialized");
|
||||||
|
typename ctx_t::socket_t sock = ctx_t::socket_t::create(_addr_internals<addr_t>::raw_t::af_value(), ctx_t::sock_type::stream(), ctx_t::proto::tcp());
|
||||||
|
sock.bind(_addr_internals<addr_t>::raw_of(addr), port);
|
||||||
|
sock.listen(queue_size);
|
||||||
|
return _socket_internals<StreamSocketsServer<ctx_t, addr_t> >::wrap(std::move(sock));
|
||||||
|
}
|
||||||
|
}
|
||||||
15
modules/sockets/src/common.cppm
Normal file
15
modules/sockets/src/common.cppm
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
|
||||||
|
# include "platform/windows.hpp"
|
||||||
|
|
||||||
|
export module ru.landgrafhomyak.BGTU.networks_1.sockets;
|
||||||
|
|
||||||
|
# include "platform/windows.cpp.inc"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
module:private;
|
||||||
271
modules/sockets/src/platform/windows.cpp.inc
Normal file
271
modules/sockets/src/platform/windows.cpp.inc
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
import std;
|
||||||
|
import ru.landgrafhomyak.BGTU.networks_1.exceptions;
|
||||||
|
import ru.landgrafhomyak.BGTU.networks_1.exceptions.windows;
|
||||||
|
import ru.landgrafhomyak.BGTU.networks_1.streams;
|
||||||
|
import :berkeley_sockets;
|
||||||
|
|
||||||
|
namespace LdH::Sockets {
|
||||||
|
export
|
||||||
|
void init_sockets() {
|
||||||
|
WORD wVersionRequested = MAKEWORD(2, 2);
|
||||||
|
WSADATA wsaData;
|
||||||
|
if (0 != WSAStartup(wVersionRequested, &wsaData))
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
export
|
||||||
|
void deinit_sockets() {
|
||||||
|
WSACleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<class _value_t, INT _family, _value_t (_any_constructor)()>
|
||||||
|
class _in_addr_wrapper {
|
||||||
|
public:
|
||||||
|
_value_t _value;
|
||||||
|
|
||||||
|
private:
|
||||||
|
_in_addr_wrapper(_value_t v) : _value{v} {
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
_in_addr_wrapper() = default;
|
||||||
|
|
||||||
|
_in_addr_wrapper(_in_addr_wrapper const &other) = default;
|
||||||
|
|
||||||
|
_in_addr_wrapper(_in_addr_wrapper &&other) = default;
|
||||||
|
|
||||||
|
_in_addr_wrapper &operator=(_in_addr_wrapper const &other) = default;
|
||||||
|
|
||||||
|
_in_addr_wrapper &operator=(_in_addr_wrapper &&other) = default;
|
||||||
|
|
||||||
|
static _in_addr_wrapper any() { return _in_addr_wrapper{_any_constructor()}; }
|
||||||
|
|
||||||
|
static _in_addr_wrapper parse(char const *raw) {
|
||||||
|
_value_t val;
|
||||||
|
switch (inet_pton(_family, raw, &val)) {
|
||||||
|
case 1:
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
throw LdH::Exception{"Wrong IP address value"};
|
||||||
|
default:
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return _in_addr_wrapper{val};
|
||||||
|
}
|
||||||
|
|
||||||
|
Berkeley::sized_ptr raw_ptr() {
|
||||||
|
return {sizeof(_value_t), &(this->_value)};
|
||||||
|
}
|
||||||
|
|
||||||
|
static INT af_value() { return _family; };
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct _WinsockContext {
|
||||||
|
struct addr {
|
||||||
|
using ipv4 = _in_addr_wrapper<in_addr, AF_INET, [] { return in4addr_any; }>;
|
||||||
|
using ipv6 = _in_addr_wrapper<in6_addr, AF_INET6, [] { return in6addr_any; }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
using af_t = int;
|
||||||
|
using sock_type_t = int;
|
||||||
|
using proto_t = int;
|
||||||
|
|
||||||
|
struct af {
|
||||||
|
public:
|
||||||
|
static int inet() { return AF_INET; }
|
||||||
|
static int inet6() { return AF_INET6; }
|
||||||
|
static int ipx() { return AF_IPX; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sock_type {
|
||||||
|
public:
|
||||||
|
static int stream() { return SOCK_STREAM; }
|
||||||
|
static int dgram() { return SOCK_DGRAM; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct proto {
|
||||||
|
public:
|
||||||
|
static int tcp() { return IPPROTO_TCP; }
|
||||||
|
static int udp() { return IPPROTO_UDP; }
|
||||||
|
static int icmp() { return IPPROTO_ICMP; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class socket_t {
|
||||||
|
private:
|
||||||
|
SOCKET _value;
|
||||||
|
|
||||||
|
explicit socket_t(SOCKET value) : _value{value} {
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
socket_t() : _value{INVALID_SOCKET} {
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t(socket_t &&other) noexcept : _value{other._value} {
|
||||||
|
other._value = INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t &operator=(socket_t &&other) noexcept {
|
||||||
|
this->_value = other._value;
|
||||||
|
other._value = INVALID_SOCKET;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~socket_t() = default;
|
||||||
|
|
||||||
|
static socket_t create(int af, int type, int proto) {
|
||||||
|
SOCKET sock = ::socket(af, type, proto);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return socket_t{sock};
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
if (0 != closesocket(this->_value)) {
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
this->_value = INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
void connect(addr::ipv4 addr, std::uint_least16_t port) {
|
||||||
|
sockaddr_in combined;
|
||||||
|
ZeroMemory(&combined, sizeof(combined));
|
||||||
|
combined.sin_family = AF_INET;
|
||||||
|
combined.sin_port = htons(port);
|
||||||
|
combined.sin_addr = addr._value;
|
||||||
|
|
||||||
|
if (0 != ::connect(this->_value, reinterpret_cast<sockaddr *>(&combined), sizeof(combined)))
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
void connect(addr::ipv6 addr, std::uint_least16_t port) {
|
||||||
|
sockaddr_in6 combined;
|
||||||
|
ZeroMemory(&combined, sizeof(combined));
|
||||||
|
combined.sin6_family = AF_INET6;
|
||||||
|
combined.sin6_port = htons(port);
|
||||||
|
combined.sin6_addr = addr._value;
|
||||||
|
|
||||||
|
if (0 != ::connect(this->_value, reinterpret_cast<sockaddr *>(&combined), sizeof(combined)))
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(addr::ipv4 addr, std::uint_least16_t port) {
|
||||||
|
sockaddr_in combined;
|
||||||
|
ZeroMemory(&combined, sizeof(combined));
|
||||||
|
combined.sin_family = AF_INET;
|
||||||
|
combined.sin_port = htons(port);
|
||||||
|
combined.sin_addr = addr._value;
|
||||||
|
|
||||||
|
if (0 != ::bind(this->_value, reinterpret_cast<sockaddr *>(&combined), sizeof(combined)))
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(addr::ipv6 addr, std::uint_least16_t port) {
|
||||||
|
sockaddr_in6 combined;
|
||||||
|
ZeroMemory(&combined, sizeof(combined));
|
||||||
|
combined.sin6_family = AF_INET6;
|
||||||
|
combined.sin6_port = htons(port);
|
||||||
|
combined.sin6_addr = addr._value;
|
||||||
|
|
||||||
|
if (0 != ::bind(this->_value, reinterpret_cast<sockaddr *>(&combined), sizeof(combined)))
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t accept(addr::ipv4 *addr) {
|
||||||
|
sockaddr_in raw_addr;
|
||||||
|
raw_addr.sin_family = AF_INET;
|
||||||
|
int size_i = sizeof(sockaddr_in);
|
||||||
|
SOCKET sock = ::accept(this->_value, reinterpret_cast<sockaddr *>(&raw_addr), &size_i);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
addr->_value = raw_addr.sin_addr;
|
||||||
|
return socket_t{sock};
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t accept(addr::ipv6 *addr) {
|
||||||
|
sockaddr_in6 raw_addr;
|
||||||
|
int size_i = sizeof(sockaddr_in6);
|
||||||
|
SOCKET sock = ::accept(this->_value, reinterpret_cast<sockaddr *>(&raw_addr), &size_i);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
addr->_value = raw_addr.sin6_addr;
|
||||||
|
return socket_t{sock};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void listen(std::size_t queue_size) {
|
||||||
|
if (0 != ::listen(this->_value, queue_size)) {
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void send_stream(std::size_t size, char const *data) {
|
||||||
|
while (true) {
|
||||||
|
std::size_t sent_count = ::send(this->_value, data, size, 0);
|
||||||
|
if (sent_count == SOCKET_ERROR) {
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
data += sent_count;
|
||||||
|
size -= sent_count;
|
||||||
|
if (size <= 0) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recv_stream(std::size_t size, char *data) {
|
||||||
|
while (true) {
|
||||||
|
std::size_t sent_count = ::recv(this->_value, data, size, 0);
|
||||||
|
if (sent_count == SOCKET_ERROR) {
|
||||||
|
LdH::throwFromWindowsErrCode(WSAGetLastError());
|
||||||
|
}
|
||||||
|
data += sent_count;
|
||||||
|
size -= sent_count;
|
||||||
|
if (size <= 0) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(Berkeley::BerkeleySocketsContext<_WinsockContext>);
|
||||||
|
|
||||||
|
|
||||||
|
export using IPv4Address = Berkeley::IPv4Address<_WinsockContext>;
|
||||||
|
export using IPv6Address = Berkeley::IPv6Address<_WinsockContext>;
|
||||||
|
|
||||||
|
export
|
||||||
|
template<class addr_t, class sock_t>
|
||||||
|
using SocketWithAddress = Berkeley::SocketWithAddress<addr_t, sock_t>;
|
||||||
|
|
||||||
|
export using StreamSocket = Berkeley::StreamSocket<_WinsockContext>;
|
||||||
|
|
||||||
|
export
|
||||||
|
template<class addr_t>
|
||||||
|
using StreamSocketsServer = Berkeley::StreamSocketsServer<_WinsockContext, addr_t>;
|
||||||
|
|
||||||
|
export
|
||||||
|
StreamSocket connect_tcp(IPv4Address addr, std::uint_least16_t port) {
|
||||||
|
return Berkeley::connect_tcp<_WinsockContext, IPv4Address>(addr, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
export
|
||||||
|
StreamSocket connect_tcp(IPv6Address addr, std::uint_least16_t port) {
|
||||||
|
return Berkeley::connect_tcp<_WinsockContext, IPv6Address>(addr, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
export
|
||||||
|
StreamSocketsServer<IPv4Address> listen_tcp(IPv4Address addr, std::uint_least16_t port, std::size_t queue_size) {
|
||||||
|
return Berkeley::listen_tcp<_WinsockContext, IPv4Address>(addr, port, queue_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
export
|
||||||
|
StreamSocketsServer<IPv6Address> listen_tcp(IPv6Address addr, std::uint_least16_t port, std::size_t queue_size) {
|
||||||
|
return Berkeley::listen_tcp<_WinsockContext, IPv6Address>(addr, port, queue_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
modules/sockets/src/platform/windows.hpp
Normal file
7
modules/sockets/src/platform/windows.hpp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2ipdef.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <in6addr.h>
|
||||||
|
|
||||||
13
modules/sockets/src/windows_binds.cppm
Normal file
13
modules/sockets/src/windows_binds.cppm
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <Winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
export module ru.landgrafhomyak.BGTU.networks_1.sockets:binds;
|
||||||
|
|
||||||
|
namespace LdH::Sockets::_Binds {
|
||||||
|
export using ::sockaddr_in;
|
||||||
|
export using ::sockaddr_in6;
|
||||||
|
export using ::SOCKET;
|
||||||
|
}
|
||||||
11
modules/streams/CMakeLists.txt
Normal file
11
modules/streams/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
add_library(streams STATIC)
|
||||||
|
|
||||||
|
target_sources(
|
||||||
|
streams
|
||||||
|
|
||||||
|
PUBLIC
|
||||||
|
FILE_SET cxx_modules TYPE CXX_MODULES FILES
|
||||||
|
src/abstract.cppm
|
||||||
|
)
|
||||||
|
|
||||||
|
#target_link_libraries(streams PRIVATE )
|
||||||
52
modules/streams/src/abstract.cppm
Normal file
52
modules/streams/src/abstract.cppm
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
export module ru.landgrafhomyak.BGTU.networks_1.streams;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
namespace LdH::Streams {
|
||||||
|
export class OutputStream {
|
||||||
|
public:
|
||||||
|
virtual void write(std::size_t, char const *) = 0;
|
||||||
|
|
||||||
|
virtual void close() = 0;
|
||||||
|
|
||||||
|
virtual ~OutputStream() noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class InputStream {
|
||||||
|
public:
|
||||||
|
virtual void read(std::size_t, char *) = 0;
|
||||||
|
|
||||||
|
virtual void close() = 0;
|
||||||
|
|
||||||
|
virtual ~InputStream() noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class OutputMessanger {
|
||||||
|
public:
|
||||||
|
virtual void sendOne(std::size_t, char const *) = 0;
|
||||||
|
|
||||||
|
virtual void close() = 0;
|
||||||
|
|
||||||
|
virtual ~OutputMessanger() noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export class InputMessanger {
|
||||||
|
public:
|
||||||
|
virtual void recvOneTruncating(std::size_t, char *) = 0;
|
||||||
|
|
||||||
|
virtual void close() = 0;
|
||||||
|
|
||||||
|
virtual ~InputMessanger() noexcept = 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module: private;
|
||||||
|
|
||||||
|
inline LdH::Streams::OutputStream::~OutputStream() = default;
|
||||||
|
|
||||||
|
inline LdH::Streams::InputStream::~InputStream() = default;
|
||||||
|
|
||||||
|
inline LdH::Streams::OutputMessanger::~OutputMessanger() = default;
|
||||||
|
|
||||||
|
inline LdH::Streams::InputMessanger::~InputMessanger() = default;
|
||||||
@ -4,5 +4,5 @@ import std;
|
|||||||
namespace LdH {
|
namespace LdH {
|
||||||
export
|
export
|
||||||
template<class lambda_t>
|
template<class lambda_t>
|
||||||
concept ThreadRoutine = std::invocable<lambda_t> && std::same_as<std::invoke_result_t<lambda_t>, void> && std::movable<lambda_t> && std::destructible<lambda_t>;
|
concept ThreadRoutine = std::invocable<lambda_t> && std::same_as<std::invoke_result_t<lambda_t>, void> /*&& std::movable<lambda_t>*/ && std::destructible<lambda_t>;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user