Commit 11dbf8f9b662668e72654bd2b979916b55e9a4ac

Authored by Patric Stout
1 parent 995dd53f

feat(packet): implement connect/publish/subscribe packets

This includes CONNACK and SUBACK.
CMakeLists.txt
... ... @@ -20,8 +20,13 @@ include(GNUInstallDirs)
20 20 add_library(${PROJECT_NAME}
21 21 src/Client.cpp
22 22 src/Connection.cpp
  23 + src/Packet.cpp
  24 +)
  25 +target_include_directories(${PROJECT_NAME}
  26 + PUBLIC include
  27 + PRIVATE src
  28 + PRIVATE vendor/magic_enum/include
23 29 )
24   -target_include_directories(${PROJECT_NAME} PUBLIC include PRIVATE src)
25 30  
26 31 find_package(Threads REQUIRED)
27 32 target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads)
... ...
src/Client.cpp
... ... @@ -163,21 +163,6 @@ void Client::unsubscribe(const std::string &topic)
163 163 }
164 164 }
165 165  
166   -void Client::Impl::sendPublish(const std::string &topic, const std::string &payload, bool retain)
167   -{
168   - LOG_TRACE(this, "Sending publish message on topic '" + topic + "': " + payload + " (" + (retain ? "retained" : "not retained") + ")");
169   -}
170   -
171   -void Client::Impl::sendSubscribe(const std::string &topic)
172   -{
173   - LOG_TRACE(this, "Sending subscribe message for topic '" + topic + "'");
174   -}
175   -
176   -void Client::Impl::sendUnsubscribe(const std::string &topic)
177   -{
178   - LOG_TRACE(this, "Sending unsubscribe message for topic '" + topic + "'");
179   -}
180   -
181 166 void Client::Impl::connectionStateChange(bool connected)
182 167 {
183 168 std::scoped_lock lock(this->state_mutex);
... ... @@ -247,3 +232,10 @@ void Client::Impl::toPublishQueue(const std::string &topic, const std::string &p
247 232 LOG_TRACE(this, "Adding message to publish queue");
248 233 this->publish_queue.push_back({topic, payload, retain});
249 234 }
  235 +
  236 +void Client::Impl::messageReceived(std::string &&topic, std::string &&payload)
  237 +{
  238 + LOG_TRACE(this, "Message received on topic '" + topic + "': " + payload);
  239 +
  240 + // TODO -- Find which subscriptions match, and call the callbacks.
  241 +}
... ...
src/ClientImpl.h
... ... @@ -46,6 +46,7 @@ public:
46 46 void sendUnsubscribe(const std::string &topic); ///< Send an unsubscribe message to the broker.
47 47 void connectionStateChange(bool connected); ///< Called when a connection goes from CONNECTING state to CONNECTED state or visa versa.
48 48 void toPublishQueue(const std::string &topic, const std::string &payload, bool retain); ///< Add a publish message to the publish queue.
  49 + void messageReceived(std::string &&topic, std::string &&payload); ///< Called when a message is received from the broker.
49 50  
50 51 State state = State::DISCONNECTED; ///< The current state of the client.
51 52 std::mutex state_mutex; ///< Mutex to protect state changes.
... ... @@ -73,4 +74,5 @@ public:
73 74 std::map<std::string, std::function<void(std::string, std::string)>> subscriptions; ///< Map of active subscriptions.
74 75  
75 76 std::unique_ptr<Connection> connection; ///< Connection to the broker.
  77 + uint16_t packet_id = 0; ///< The next packet ID to use. Will overflow on 65535 to 0.
76 78 };
... ...
src/Connection.cpp
... ... @@ -19,12 +19,14 @@
19 19 Connection::Connection(TrueMQTT::Client::LogLevel log_level,
20 20 const std::function<void(TrueMQTT::Client::LogLevel, std::string)> logger,
21 21 const std::function<void(TrueMQTT::Client::Error, std::string)> error_callback,
  22 + const std::function<void(std::string &&, std::string &&)> publish_callback,
22 23 const std::function<void(bool)> connection_change_callback,
23 24 const std::string &host,
24 25 int port)
25 26 : log_level(log_level),
26 27 logger(std::move(logger)),
27 28 m_error_callback(std::move(error_callback)),
  29 + m_publish_callback(std::move(publish_callback)),
28 30 m_connection_change_callback(std::move(connection_change_callback)),
29 31 m_host(host),
30 32 m_port(port),
... ... @@ -49,7 +51,7 @@ Connection::~Connection()
49 51 }
50 52 }
51 53  
52   -std::string Connection::addrinfo_to_string(addrinfo *address)
  54 +std::string Connection::addrinfoToString(addrinfo *address)
53 55 {
54 56 char host[NI_MAXHOST];
55 57 getnameinfo(address->ai_addr, address->ai_addrlen, host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
... ... @@ -68,7 +70,7 @@ void Connection::run()
68 70 break;
69 71  
70 72 case State::CONNECTING:
71   - if (!connect_to_any())
  73 + if (!connectToAny())
72 74 {
73 75 m_state = State::BACKOFF;
74 76 }
... ... @@ -83,28 +85,19 @@ void Connection::run()
83 85 m_state = State::RESOLVING;
84 86 break;
85 87  
  88 + case State::AUTHENTICATING:
86 89 case State::CONNECTED:
87 90 {
88   - char buf[9000];
89   - ssize_t res = recv(m_socket, buf, sizeof(buf), 0);
90   -
91   - if (res == 0)
92   - {
93   - LOG_WARNING(this, "Connection closed by peer");
94   - m_state = State::BACKOFF;
95   - m_connection_change_callback(false);
96   - }
97   - else if (res < 0)
  91 + if (!recvLoop())
98 92 {
99   - LOG_WARNING(this, "Connection read error: " + std::string(strerror(errno)));
  93 + if (m_socket != INVALID_SOCKET)
  94 + {
  95 + closesocket(m_socket);
  96 + m_socket = INVALID_SOCKET;
  97 + }
100 98 m_state = State::BACKOFF;
101 99 m_connection_change_callback(false);
102 100 }
103   - else
104   - {
105   - LOG_TRACE(this, "Received " + std::to_string(res) + " bytes");
106   - }
107   -
108 101 break;
109 102 }
110 103 }
... ... @@ -177,7 +170,7 @@ void Connection::resolve()
177 170 LOG_DEBUG(this, "Resolved hostname '" + m_host + "' to:");
178 171 for (addrinfo *res : m_addresses)
179 172 {
180   - LOG_DEBUG(this, "- " + addrinfo_to_string(res));
  173 + LOG_DEBUG(this, "- " + addrinfoToString(res));
181 174 }
182 175 }
183 176 #endif
... ... @@ -192,12 +185,12 @@ void Connection::resolve()
192 185 m_state = State::CONNECTING;
193 186 }
194 187  
195   -bool Connection::connect_to_any()
  188 +bool Connection::connectToAny()
196 189 {
197 190 // Check if we have pending attempts. If not, queue a new attempt.
198 191 if (m_sockets.empty())
199 192 {
200   - return try_next_address();
  193 + return tryNextAddress();
201 194 }
202 195  
203 196 // Check for at most 100ms if there is any activity on the sockets.
... ... @@ -231,7 +224,7 @@ bool Connection::connect_to_any()
231 224 }
232 225  
233 226 // Try to queue the next address for a connection.
234   - if (try_next_address())
  227 + if (tryNextAddress())
235 228 {
236 229 return true;
237 230 }
... ... @@ -269,7 +262,7 @@ bool Connection::connect_to_any()
269 262 if (err != 0)
270 263 {
271 264 // It is in error-state: report about it, and remove it.
272   - LOG_ERROR(this, "Could not connect to " + addrinfo_to_string(m_socket_to_address[*socket_it]) + ": " + std::string(strerror(err)));
  265 + LOG_ERROR(this, "Could not connect to " + addrinfoToString(m_socket_to_address[*socket_it]) + ": " + std::string(strerror(err)));
273 266 closesocket(*socket_it);
274 267 m_socket_to_address.erase(*socket_it);
275 268 socket_it = m_sockets.erase(socket_it);
... ... @@ -291,7 +284,7 @@ bool Connection::connect_to_any()
291 284 }
292 285  
293 286 // We have a connected socket.
294   - LOG_DEBUG(this, "Connected to " + addrinfo_to_string(m_socket_to_address[socket_connected]));
  287 + LOG_DEBUG(this, "Connected to " + addrinfoToString(m_socket_to_address[socket_connected]));
295 288  
296 289 // Close all other pending connections.
297 290 for (const auto &socket : m_sockets)
... ... @@ -312,12 +305,12 @@ bool Connection::connect_to_any()
312 305 }
313 306  
314 307 m_socket = socket_connected;
315   - m_state = State::CONNECTED;
316   - m_connection_change_callback(true);
  308 + sendConnect();
  309 + m_state = State::AUTHENTICATING;
317 310 return true;
318 311 }
319 312  
320   -bool Connection::try_next_address()
  313 +bool Connection::tryNextAddress()
321 314 {
322 315 if (m_address_current >= m_addresses.size())
323 316 {
... ... @@ -356,7 +349,7 @@ void Connection::connect(addrinfo *address)
356 349 }
357 350  
358 351 // Start the actual connection attempt.
359   - LOG_DEBUG(this, "Connecting to " + addrinfo_to_string(address));
  352 + LOG_DEBUG(this, "Connecting to " + addrinfoToString(address));
360 353 int err = ::connect(sock, address->ai_addr, (int)address->ai_addrlen);
361 354 if (err != 0 && errno != EINPROGRESS)
362 355 {
... ... @@ -364,7 +357,7 @@ void Connection::connect(addrinfo *address)
364 357 // else, something is wrong. Report the error and close the socket.
365 358 closesocket(sock);
366 359  
367   - LOG_ERROR(this, "Could not connect to " + addrinfo_to_string(address) + ": " + std::string(strerror(errno)));
  360 + LOG_ERROR(this, "Could not connect to " + addrinfoToString(address) + ": " + std::string(strerror(errno)));
368 361 return;
369 362 }
370 363  
... ... @@ -376,7 +369,10 @@ void Connection::connect(addrinfo *address)
376 369 void TrueMQTT::Client::Impl::connect()
377 370 {
378 371 this->connection = std::make_unique<Connection>(
379   - this->log_level, this->logger, this->error_callback, [this](bool connected)
  372 + this->log_level, this->logger, this->error_callback,
  373 + [this](std::string &&topic, std::string &&payload)
  374 + { this->messageReceived(std::move(topic), std::move(payload)); },
  375 + [this](bool connected)
380 376 { this->connectionStateChange(connected); },
381 377 this->host, this->port);
382 378 }
... ...
src/Connection.h
... ... @@ -26,23 +26,33 @@ public:
26 26 Connection(TrueMQTT::Client::LogLevel log_level,
27 27 const std::function<void(TrueMQTT::Client::LogLevel, std::string)> logger,
28 28 const std::function<void(TrueMQTT::Client::Error, std::string)> error_callback,
  29 + const std::function<void(std::string &&, std::string &&)> publish_callback,
29 30 const std::function<void(bool)> connection_change_callback,
30 31 const std::string &host,
31 32 int port);
32 33 ~Connection();
33 34  
  35 + void send(class Packet &packet);
  36 +
34 37 private:
  38 + // Implemented in Connection.cpp
35 39 void run();
36 40 void resolve();
37   - bool try_next_address();
  41 + bool tryNextAddress();
38 42 void connect(addrinfo *address);
39   - bool connect_to_any();
40   - std::string addrinfo_to_string(addrinfo *address);
  43 + bool connectToAny();
  44 + std::string addrinfoToString(addrinfo *address);
  45 +
  46 + // Implemented in Packet.cpp
  47 + ssize_t recv(char *buffer, size_t length);
  48 + bool recvLoop();
  49 + void sendConnect();
41 50  
42 51 enum class State
43 52 {
44 53 RESOLVING,
45 54 CONNECTING,
  55 + AUTHENTICATING,
46 56 CONNECTED,
47 57 BACKOFF,
48 58 };
... ... @@ -51,6 +61,7 @@ private:
51 61 const std::function<void(TrueMQTT::Client::LogLevel, std::string)> logger;
52 62  
53 63 const std::function<void(TrueMQTT::Client::Error, std::string)> m_error_callback;
  64 + const std::function<void(std::string &&, std::string &&)> m_publish_callback;
54 65 const std::function<void(bool)> m_connection_change_callback;
55 66  
56 67 const std::string &m_host; ///< The hostname or IP address to connect to.
... ...
src/Packet.cpp 0 → 100644
  1 +/*
  2 + * Copyright (c) TrueBrain
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +
  8 +#include "ClientImpl.h"
  9 +#include "Connection.h"
  10 +#include "Log.h"
  11 +
  12 +#include "magic_enum.hpp"
  13 +
  14 +#include <string.h>
  15 +
  16 +class Packet
  17 +{
  18 +public:
  19 + enum class PacketType
  20 + {
  21 + CONNECT = 1,
  22 + CONNACK = 2,
  23 + PUBLISH = 3,
  24 + PUBACK = 4,
  25 + PUBREC = 5,
  26 + PUBREL = 6,
  27 + PUBCOMP = 7,
  28 + SUBSCRIBE = 8,
  29 + SUBACK = 9,
  30 + UNSUBSCRIBE = 10,
  31 + UNSUBACK = 11,
  32 + PINGREQ = 12,
  33 + PINGRESP = 13,
  34 + DISCONNECT = 14,
  35 + };
  36 +
  37 + Packet(PacketType packet_type, uint8_t flags)
  38 + : m_packet_type(packet_type),
  39 + m_flags(flags)
  40 + {
  41 + }
  42 +
  43 + Packet(PacketType packet_type, uint8_t flags, std::vector<uint8_t> &&data)
  44 + : m_buffer(std::move(data)),
  45 + m_packet_type(packet_type),
  46 + m_flags(flags)
  47 + {
  48 + }
  49 +
  50 + void write_uint8(uint8_t value)
  51 + {
  52 + m_buffer.push_back(value);
  53 + }
  54 +
  55 + void write_uint16(uint16_t value)
  56 + {
  57 + m_buffer.push_back(value >> 8);
  58 + m_buffer.push_back(value & 0xFF);
  59 + }
  60 +
  61 + void write(const char *data, size_t length)
  62 + {
  63 + m_buffer.insert(m_buffer.end(), data, data + length);
  64 + }
  65 +
  66 + void write_string(const std::string &str)
  67 + {
  68 + write_uint16(str.size());
  69 + write(str.c_str(), str.size());
  70 + }
  71 +
  72 + bool read_uint8(uint8_t &value)
  73 + {
  74 + if (m_buffer.size() < m_read_offset + 1)
  75 + {
  76 + return false;
  77 + }
  78 + value = m_buffer[m_read_offset++];
  79 + return true;
  80 + }
  81 +
  82 + bool read_uint16(uint16_t &value)
  83 + {
  84 + if (m_buffer.size() < m_read_offset + 2)
  85 + {
  86 + return false;
  87 + }
  88 + value = m_buffer[m_read_offset++] << 8;
  89 + value |= m_buffer[m_read_offset++];
  90 + return true;
  91 + }
  92 +
  93 + bool read_string(std::string &str)
  94 + {
  95 + uint16_t length;
  96 + if (!read_uint16(length))
  97 + {
  98 + return false;
  99 + }
  100 + if (m_buffer.size() < m_read_offset + length)
  101 + {
  102 + return false;
  103 + }
  104 + const char *data = reinterpret_cast<const char *>(m_buffer.data()) + m_read_offset;
  105 + str.assign(data, length);
  106 + m_read_offset += length;
  107 + return true;
  108 + }
  109 +
  110 + void read_remaining(std::string &str)
  111 + {
  112 + const char *data = reinterpret_cast<const char *>(m_buffer.data()) + m_read_offset;
  113 + str.assign(data, m_buffer.size() - m_read_offset);
  114 + m_read_offset = m_buffer.size();
  115 + }
  116 +
  117 + std::vector<uint8_t> m_buffer;
  118 + size_t m_read_offset = 0;
  119 +
  120 + PacketType m_packet_type;
  121 + uint8_t m_flags;
  122 +};
  123 +
  124 +ssize_t Connection::recv(char *buffer, size_t length)
  125 +{
  126 + ssize_t res = ::recv(m_socket, buffer, length, 0);
  127 + if (res == 0)
  128 + {
  129 + LOG_INFO(this, "Connection closed by peer");
  130 + return -1;
  131 + }
  132 + if (res < 0)
  133 + {
  134 + LOG_WARNING(this, "Connection read error: " + std::string(strerror(errno)));
  135 + return -1;
  136 + }
  137 +
  138 + LOG_TRACE(this, "Received " + std::to_string(res) + " bytes");
  139 + return res;
  140 +}
  141 +
  142 +bool Connection::recvLoop()
  143 +{
  144 + uint8_t buffer;
  145 +
  146 + // Read the header of the next packet.
  147 + if (recv((char *)&buffer, 1) <= 0)
  148 + {
  149 + return false;
  150 + }
  151 +
  152 + uint8_t packet_type_raw = buffer >> 4;
  153 + uint8_t flags = buffer & 0x0F;
  154 +
  155 + if (packet_type_raw < 1 || packet_type_raw > 14)
  156 + {
  157 + LOG_ERROR(this, "Received invalid packet type (" + std::to_string(packet_type_raw) + ") from broker, closing connection");
  158 + return false;
  159 + }
  160 +
  161 + Packet::PacketType packet_type = static_cast<Packet::PacketType>(packet_type_raw);
  162 +
  163 + // Read the length of the packet. This is a bit slow to read, as only
  164 + // after reading the byte we know if another byte follows.
  165 + // If the high-bit is set, it means there is another byte. Up to 4 bytes.
  166 + uint32_t remaining_length = 0;
  167 + for (int i = 0; i < 4; i++)
  168 + {
  169 + if (recv((char *)&buffer, 1) <= 0)
  170 + {
  171 + return false;
  172 + }
  173 +
  174 + remaining_length |= (buffer & 0x7F) << (7 * i);
  175 +
  176 + if ((buffer & 0x80) == 0)
  177 + {
  178 + break;
  179 + }
  180 + }
  181 + if ((buffer & 0x80) != 0)
  182 + {
  183 + LOG_ERROR(this, "Malformed packet length received, closing connection");
  184 + return false;
  185 + }
  186 +
  187 + LOG_TRACE(this, "Received packet of type " + std::string(magic_enum::enum_name(packet_type)) + " with flags " + std::to_string(flags) + " and length " + std::to_string(remaining_length));
  188 +
  189 + // Read the rest of the packet.
  190 + std::vector<uint8_t> data;
  191 + data.resize(remaining_length);
  192 +
  193 + ssize_t pos = 0;
  194 + while (pos != remaining_length)
  195 + {
  196 + ssize_t res = recv((char *)&data[pos], remaining_length - pos);
  197 + if (res <= 0)
  198 + {
  199 + return false;
  200 + }
  201 + pos += res;
  202 + }
  203 +
  204 + Packet packet(packet_type, flags, std::move(data));
  205 +
  206 + switch (packet_type)
  207 + {
  208 + case Packet::PacketType::CONNACK:
  209 + {
  210 + uint8_t acknowledge;
  211 + uint8_t return_code;
  212 +
  213 + if (!packet.read_uint8(acknowledge))
  214 + {
  215 + LOG_ERROR(this, "Malformed packet received, closing connection");
  216 + return false;
  217 + }
  218 + if (!packet.read_uint8(return_code))
  219 + {
  220 + LOG_ERROR(this, "Malformed packet received, closing connection");
  221 + return false;
  222 + }
  223 +
  224 + LOG_DEBUG(this, "Received CONNACK with acknowledge " + std::to_string(acknowledge) + " and return code " + std::to_string(return_code));
  225 +
  226 + if (return_code != 0)
  227 + {
  228 + LOG_ERROR(this, "Broker actively refused our connection");
  229 + return false;
  230 + }
  231 +
  232 + m_state = State::CONNECTED;
  233 + m_connection_change_callback(true);
  234 + break;
  235 + }
  236 + case Packet::PacketType::PUBLISH:
  237 + {
  238 + std::string topic;
  239 + if (!packet.read_string(topic))
  240 + {
  241 + LOG_ERROR(this, "Malformed packet received, closing connection");
  242 + return false;
  243 + }
  244 +
  245 + std::string payload;
  246 + packet.read_remaining(payload);
  247 +
  248 + LOG_DEBUG(this, "Received PUBLISH with topic " + topic + ": " + payload);
  249 +
  250 + m_publish_callback(std::move(topic), std::move(payload));
  251 + break;
  252 + }
  253 + case Packet::PacketType::SUBACK:
  254 + {
  255 + uint16_t packet_id;
  256 + uint8_t return_code;
  257 +
  258 + if (!packet.read_uint16(packet_id))
  259 + {
  260 + LOG_ERROR(this, "Malformed packet received, closing connection");
  261 + return false;
  262 + }
  263 + if (!packet.read_uint8(return_code))
  264 + {
  265 + LOG_ERROR(this, "Malformed packet received, closing connection");
  266 + return false;
  267 + }
  268 +
  269 + LOG_DEBUG(this, "Received SUBACK with packet id " + std::to_string(packet_id) + " and return code " + std::to_string(return_code));
  270 +
  271 + if (return_code > 2)
  272 + {
  273 + LOG_WARNING(this, "Broker refused our subscription");
  274 + // TODO -- Keep track of the topic per ticket
  275 + m_error_callback(TrueMQTT::Client::Error::SUBSCRIBE_FAILED, "");
  276 + }
  277 +
  278 + break;
  279 + }
  280 + default:
  281 + LOG_ERROR(this, "Received unexpected packet type " + std::string(magic_enum::enum_name(packet_type)) + " from broker, closing connection");
  282 + return false;
  283 + }
  284 +
  285 + return true;
  286 +}
  287 +
  288 +void Connection::send(Packet &packet)
  289 +{
  290 + LOG_TRACE(this, "Sending packet of type " + std::string(magic_enum::enum_name(packet.m_packet_type)) + " with flags " + std::to_string(packet.m_flags) + " and length " + std::to_string(packet.m_buffer.size()));
  291 +
  292 + std::vector<uint8_t> buffer;
  293 +
  294 + // Create the header.
  295 + buffer.push_back((static_cast<uint8_t>(packet.m_packet_type) << 4) | packet.m_flags);
  296 +
  297 + // Calculate the length buffer.
  298 + size_t length = packet.m_buffer.size();
  299 + do
  300 + {
  301 + uint8_t byte = length & 0x7F;
  302 + length >>= 7;
  303 + if (length > 0)
  304 + {
  305 + byte |= 0x80;
  306 + }
  307 + buffer.push_back(byte);
  308 + } while (length > 0);
  309 +
  310 + // Write header and packet.
  311 + if (::send(m_socket, (char *)buffer.data(), buffer.size(), MSG_NOSIGNAL) < 0)
  312 + {
  313 + LOG_WARNING(this, "Connection write error: " + std::string(strerror(errno)));
  314 + return;
  315 + }
  316 + if (::send(m_socket, (char *)packet.m_buffer.data(), packet.m_buffer.size(), MSG_NOSIGNAL) < 0)
  317 + {
  318 + LOG_WARNING(this, "Connection write error: " + std::string(strerror(errno)));
  319 + return;
  320 + }
  321 +}
  322 +
  323 +void Connection::sendConnect()
  324 +{
  325 + LOG_TRACE(this, "Sending CONNECT packet");
  326 +
  327 + static std::string protocol_name("MQTT");
  328 + static std::string client_id("ClientID");
  329 +
  330 + uint8_t flags = 0;
  331 + flags |= 1 << 1; // Clean session
  332 + // TODO -- Support for last-will
  333 +
  334 + Packet packet(Packet::PacketType::CONNECT, 0);
  335 +
  336 + packet.write_string(protocol_name); // Protocol name
  337 + packet.write_uint8(4); // Protocol level
  338 +
  339 + packet.write_uint8(flags);
  340 + packet.write_uint16(30); // Keep-alive
  341 +
  342 + packet.write_string(client_id); // Client ID
  343 + // TODO -- Last will topic & message
  344 +
  345 + send(packet);
  346 +}
  347 +
  348 +void TrueMQTT::Client::Impl::sendPublish(const std::string &topic, const std::string &payload, bool retain)
  349 +{
  350 + LOG_TRACE(this, "Sending PUBLISH packet to topic '" + topic + "': " + payload + " (" + (retain ? "retained" : "not retained") + ")");
  351 +
  352 + uint8_t flags = 0;
  353 + flags |= (retain ? 1 : 0) << 0; // Retain
  354 + flags |= 0 << 1; // QoS
  355 + flags |= 0 << 3; // DUP
  356 +
  357 + Packet packet(Packet::PacketType::PUBLISH, flags);
  358 +
  359 + packet.write_string(topic);
  360 + packet.write(payload.c_str(), payload.size());
  361 +
  362 + connection->send(packet);
  363 +}
  364 +
  365 +void TrueMQTT::Client::Impl::sendSubscribe(const std::string &topic)
  366 +{
  367 + LOG_TRACE(this, "Sending SUBSCRIBE packet for topic '" + topic + "'");
  368 +
  369 + Packet packet(Packet::PacketType::SUBSCRIBE, 2);
  370 +
  371 + // By specs, packet-id zero is not allowed.
  372 + if (packet_id == 0)
  373 + {
  374 + packet_id++;
  375 + }
  376 +
  377 + packet.write_uint16(packet_id++);
  378 + packet.write_string(topic);
  379 + packet.write_uint8(0); // QoS
  380 +
  381 + connection->send(packet);
  382 +}
  383 +
  384 +void TrueMQTT::Client::Impl::sendUnsubscribe(const std::string &topic)
  385 +{
  386 + LOG_TRACE(this, "Sending unsubscribe message for topic '" + topic + "'");
  387 +}
... ...
vendor/magic_enum/LICENSE 0 → 100644
  1 +MIT License
  2 +
  3 +Copyright (c) 2019 - 2022 Daniil Goncharov
  4 +
  5 +Permission is hereby granted, free of charge, to any person obtaining a copy
  6 +of this software and associated documentation files (the "Software"), to deal
  7 +in the Software without restriction, including without limitation the rights
  8 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9 +copies of the Software, and to permit persons to whom the Software is
  10 +furnished to do so, subject to the following conditions:
  11 +
  12 +The above copyright notice and this permission notice shall be included in all
  13 +copies or substantial portions of the Software.
  14 +
  15 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21 +SOFTWARE.
... ...
vendor/magic_enum/README.md 0 → 100644
  1 +# Magic Enum
  2 +
  3 +This is v0.8.1 of [magic_enum](https://github.com/Neargye/magic_enum).
  4 +
  5 +See upstream for more details about magic_enum.
  6 +
... ...
vendor/magic_enum/include/magic_enum.hpp 0 → 100644
  1 +// __ __ _ ______ _____
  2 +// | \/ | (_) | ____| / ____|_ _
  3 +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_
  4 +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _|
  5 +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_|
  6 +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____|
  7 +// __/ | https://github.com/Neargye/magic_enum
  8 +// |___/ version 0.8.1
  9 +//
  10 +// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
  11 +// SPDX-License-Identifier: MIT
  12 +// Copyright (c) 2019 - 2022 Daniil Goncharov <neargye@gmail.com>.
  13 +//
  14 +// Permission is hereby granted, free of charge, to any person obtaining a copy
  15 +// of this software and associated documentation files (the "Software"), to deal
  16 +// in the Software without restriction, including without limitation the rights
  17 +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  18 +// copies of the Software, and to permit persons to whom the Software is
  19 +// furnished to do so, subject to the following conditions:
  20 +//
  21 +// The above copyright notice and this permission notice shall be included in all
  22 +// copies or substantial portions of the Software.
  23 +//
  24 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  25 +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  26 +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  27 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  28 +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  29 +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  30 +// SOFTWARE.
  31 +
  32 +#ifndef NEARGYE_MAGIC_ENUM_HPP
  33 +#define NEARGYE_MAGIC_ENUM_HPP
  34 +
  35 +#define MAGIC_ENUM_VERSION_MAJOR 0
  36 +#define MAGIC_ENUM_VERSION_MINOR 8
  37 +#define MAGIC_ENUM_VERSION_PATCH 1
  38 +
  39 +#include <array>
  40 +#include <cassert>
  41 +#include <cstdint>
  42 +#include <cstddef>
  43 +#include <iosfwd>
  44 +#include <limits>
  45 +#include <type_traits>
  46 +#include <utility>
  47 +#include <variant>
  48 +
  49 +#if defined(MAGIC_ENUM_CONFIG_FILE)
  50 +#include MAGIC_ENUM_CONFIG_FILE
  51 +#endif
  52 +
  53 +#if !defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL)
  54 +#include <optional>
  55 +#endif
  56 +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING)
  57 +#include <string>
  58 +#endif
  59 +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW)
  60 +#include <string_view>
  61 +#endif
  62 +
  63 +#if defined(__clang__)
  64 +# pragma clang diagnostic push
  65 +#elif defined(__GNUC__)
  66 +# pragma GCC diagnostic push
  67 +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'.
  68 +#elif defined(_MSC_VER)
  69 +# pragma warning(push)
  70 +# pragma warning(disable : 26495) // Variable 'static_string<N>::chars_' is uninitialized.
  71 +# pragma warning(disable : 28020) // Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value.
  72 +# pragma warning(disable : 26451) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call.
  73 +# pragma warning(disable : 4514) // Unreferenced inline function has been removed.
  74 +#endif
  75 +
  76 +// Checks magic_enum compiler compatibility.
  77 +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910
  78 +# undef MAGIC_ENUM_SUPPORTED
  79 +# define MAGIC_ENUM_SUPPORTED 1
  80 +#endif
  81 +
  82 +// Checks magic_enum compiler aliases compatibility.
  83 +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920
  84 +# undef MAGIC_ENUM_SUPPORTED_ALIASES
  85 +# define MAGIC_ENUM_SUPPORTED_ALIASES 1
  86 +#endif
  87 +
  88 +// Enum value must be greater or equals than MAGIC_ENUM_RANGE_MIN. By default MAGIC_ENUM_RANGE_MIN = -128.
  89 +// If need another min range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN.
  90 +#if !defined(MAGIC_ENUM_RANGE_MIN)
  91 +# define MAGIC_ENUM_RANGE_MIN -128
  92 +#endif
  93 +
  94 +// Enum value must be less or equals than MAGIC_ENUM_RANGE_MAX. By default MAGIC_ENUM_RANGE_MAX = 128.
  95 +// If need another max range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MAX.
  96 +#if !defined(MAGIC_ENUM_RANGE_MAX)
  97 +# define MAGIC_ENUM_RANGE_MAX 128
  98 +#endif
  99 +
  100 +namespace magic_enum {
  101 +
  102 +// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_OPTIONAL.
  103 +#if defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL)
  104 +MAGIC_ENUM_USING_ALIAS_OPTIONAL
  105 +#else
  106 +using std::optional;
  107 +#endif
  108 +
  109 +// If need another string_view type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW.
  110 +#if defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW)
  111 +MAGIC_ENUM_USING_ALIAS_STRING_VIEW
  112 +#else
  113 +using std::string_view;
  114 +#endif
  115 +
  116 +// If need another string type, define the macro MAGIC_ENUM_USING_ALIAS_STRING.
  117 +#if defined(MAGIC_ENUM_USING_ALIAS_STRING)
  118 +MAGIC_ENUM_USING_ALIAS_STRING
  119 +#else
  120 +using std::string;
  121 +#endif
  122 +
  123 +namespace customize {
  124 +
  125 +// Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.
  126 +// If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX.
  127 +// If need another range for specific enum type, add specialization enum_range for necessary enum type.
  128 +template <typename E>
  129 +struct enum_range {
  130 + static_assert(std::is_enum_v<E>, "magic_enum::customize::enum_range requires enum type.");
  131 + static constexpr int min = MAGIC_ENUM_RANGE_MIN;
  132 + static constexpr int max = MAGIC_ENUM_RANGE_MAX;
  133 + static_assert(max > min, "magic_enum::customize::enum_range requires max > min.");
  134 +};
  135 +
  136 +static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN.");
  137 +static_assert((MAGIC_ENUM_RANGE_MAX - MAGIC_ENUM_RANGE_MIN) < (std::numeric_limits<std::uint16_t>::max)(), "MAGIC_ENUM_RANGE must be less than UINT16_MAX.");
  138 +
  139 +namespace detail {
  140 +enum class default_customize_tag {};
  141 +enum class invalid_customize_tag {};
  142 +} // namespace magic_enum::customize::detail
  143 +
  144 +using customize_t = std::variant<string_view, detail::default_customize_tag, detail::invalid_customize_tag>;
  145 +
  146 +// Default customize.
  147 +inline constexpr auto default_tag = detail::default_customize_tag{};
  148 +// Invalid customize.
  149 +inline constexpr auto invalid_tag = detail::invalid_customize_tag{};
  150 +
  151 +// If need custom names for enum, add specialization enum_name for necessary enum type.
  152 +template <typename E>
  153 +constexpr customize_t enum_name(E) noexcept {
  154 + return default_tag;
  155 +}
  156 +
  157 +// If need custom type name for enum, add specialization enum_type_name for necessary enum type.
  158 +template <typename E>
  159 +constexpr customize_t enum_type_name() noexcept {
  160 + return default_tag;
  161 +}
  162 +
  163 +} // namespace magic_enum::customize
  164 +
  165 +namespace detail {
  166 +
  167 +template <auto V, typename = std::enable_if_t<std::is_enum_v<std::decay_t<decltype(V)>>>>
  168 +using enum_constant = std::integral_constant<std::decay_t<decltype(V)>, V>;
  169 +
  170 +template <typename... T>
  171 +inline constexpr bool always_false_v = false;
  172 +
  173 +template <typename T>
  174 +struct supported
  175 +#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED || defined(MAGIC_ENUM_NO_CHECK_SUPPORT)
  176 + : std::true_type {};
  177 +#else
  178 + : std::false_type {};
  179 +#endif
  180 +
  181 +template <typename T, typename = void>
  182 +struct has_is_flags : std::false_type {};
  183 +
  184 +template <typename T>
  185 +struct has_is_flags<T, std::void_t<decltype(customize::enum_range<T>::is_flags)>> : std::bool_constant<std::is_same_v<bool, std::decay_t<decltype(customize::enum_range<T>::is_flags)>>> {};
  186 +
  187 +template <typename T, typename = void>
  188 +struct range_min : std::integral_constant<int, MAGIC_ENUM_RANGE_MIN> {};
  189 +
  190 +template <typename T>
  191 +struct range_min<T, std::void_t<decltype(customize::enum_range<T>::min)>> : std::integral_constant<decltype(customize::enum_range<T>::min), customize::enum_range<T>::min> {};
  192 +
  193 +template <typename T, typename = void>
  194 +struct range_max : std::integral_constant<int, MAGIC_ENUM_RANGE_MAX> {};
  195 +
  196 +template <typename T>
  197 +struct range_max<T, std::void_t<decltype(customize::enum_range<T>::max)>> : std::integral_constant<decltype(customize::enum_range<T>::max), customize::enum_range<T>::max> {};
  198 +
  199 +template <std::size_t N>
  200 +class static_string {
  201 + public:
  202 + constexpr explicit static_string(string_view str) noexcept : static_string{str, std::make_index_sequence<N>{}} {
  203 + assert(str.size() == N);
  204 + }
  205 +
  206 + constexpr const char* data() const noexcept { return chars_; }
  207 +
  208 + constexpr std::size_t size() const noexcept { return N; }
  209 +
  210 + constexpr operator string_view() const noexcept { return {data(), size()}; }
  211 +
  212 + private:
  213 + template <std::size_t... I>
  214 + constexpr static_string(string_view str, std::index_sequence<I...>) noexcept : chars_{str[I]..., '\0'} {}
  215 +
  216 + char chars_[N + 1];
  217 +};
  218 +
  219 +template <>
  220 +class static_string<0> {
  221 + public:
  222 + constexpr explicit static_string() = default;
  223 +
  224 + constexpr explicit static_string(string_view) noexcept {}
  225 +
  226 + constexpr const char* data() const noexcept { return nullptr; }
  227 +
  228 + constexpr std::size_t size() const noexcept { return 0; }
  229 +
  230 + constexpr operator string_view() const noexcept { return {}; }
  231 +};
  232 +
  233 +constexpr string_view pretty_name(string_view name) noexcept {
  234 + for (std::size_t i = name.size(); i > 0; --i) {
  235 + if (!((name[i - 1] >= '0' && name[i - 1] <= '9') ||
  236 + (name[i - 1] >= 'a' && name[i - 1] <= 'z') ||
  237 + (name[i - 1] >= 'A' && name[i - 1] <= 'Z') ||
  238 +#if defined(MAGIC_ENUM_ENABLE_NONASCII)
  239 + (name[i - 1] & 0x80) ||
  240 +#endif
  241 + (name[i - 1] == '_'))) {
  242 + name.remove_prefix(i);
  243 + break;
  244 + }
  245 + }
  246 +
  247 + if (name.size() > 0 && ((name.front() >= 'a' && name.front() <= 'z') ||
  248 + (name.front() >= 'A' && name.front() <= 'Z') ||
  249 +#if defined(MAGIC_ENUM_ENABLE_NONASCII)
  250 + (name.front() & 0x80) ||
  251 +#endif
  252 + (name.front() == '_'))) {
  253 + return name;
  254 + }
  255 +
  256 + return {}; // Invalid name.
  257 +}
  258 +
  259 +class case_insensitive {
  260 + static constexpr char to_lower(char c) noexcept {
  261 + return (c >= 'A' && c <= 'Z') ? static_cast<char>(c + ('a' - 'A')) : c;
  262 + }
  263 +
  264 + public:
  265 + template <typename L, typename R>
  266 + constexpr auto operator()([[maybe_unused]] L lhs, [[maybe_unused]] R rhs) const noexcept -> std::enable_if_t<std::is_same_v<std::decay_t<L>, char> && std::is_same_v<std::decay_t<R>, char>, bool> {
  267 +#if defined(MAGIC_ENUM_ENABLE_NONASCII)
  268 + static_assert(always_false_v<L, R>, "magic_enum::case_insensitive not supported Non-ASCII feature.");
  269 + return false;
  270 +#else
  271 + return to_lower(lhs) == to_lower(rhs);
  272 +#endif
  273 + }
  274 +};
  275 +
  276 +constexpr std::size_t find(string_view str, char c) noexcept {
  277 +#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__)
  278 +// https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc
  279 +// https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html
  280 + constexpr bool workaround = true;
  281 +#else
  282 + constexpr bool workaround = false;
  283 +#endif
  284 +
  285 + if constexpr (workaround) {
  286 + for (std::size_t i = 0; i < str.size(); ++i) {
  287 + if (str[i] == c) {
  288 + return i;
  289 + }
  290 + }
  291 +
  292 + return string_view::npos;
  293 + } else {
  294 + return str.find_first_of(c);
  295 + }
  296 +}
  297 +
  298 +template <typename T, std::size_t N, std::size_t... I>
  299 +constexpr std::array<std::remove_cv_t<T>, N> to_array(T (&a)[N], std::index_sequence<I...>) noexcept {
  300 + return {{a[I]...}};
  301 +}
  302 +
  303 +template <typename BinaryPredicate>
  304 +constexpr bool is_default_predicate() noexcept {
  305 + return std::is_same_v<std::decay_t<BinaryPredicate>, std::equal_to<string_view::value_type>> ||
  306 + std::is_same_v<std::decay_t<BinaryPredicate>, std::equal_to<>>;
  307 +}
  308 +
  309 +template <typename BinaryPredicate>
  310 +constexpr bool is_nothrow_invocable() {
  311 + return is_default_predicate<BinaryPredicate>() ||
  312 + std::is_nothrow_invocable_r_v<bool, BinaryPredicate, char, char>;
  313 +}
  314 +
  315 +template <typename BinaryPredicate>
  316 +constexpr bool cmp_equal(string_view lhs, string_view rhs, [[maybe_unused]] BinaryPredicate&& p) noexcept(is_nothrow_invocable<BinaryPredicate>()) {
  317 +#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__)
  318 + // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html
  319 + // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html
  320 + constexpr bool workaround = true;
  321 +#else
  322 + constexpr bool workaround = false;
  323 +#endif
  324 +
  325 + if constexpr (!is_default_predicate<BinaryPredicate>() || workaround) {
  326 + if (lhs.size() != rhs.size()) {
  327 + return false;
  328 + }
  329 +
  330 + const auto size = lhs.size();
  331 + for (std::size_t i = 0; i < size; ++i) {
  332 + if (!p(lhs[i], rhs[i])) {
  333 + return false;
  334 + }
  335 + }
  336 +
  337 + return true;
  338 + } else {
  339 + return lhs == rhs;
  340 + }
  341 +}
  342 +
  343 +template <typename L, typename R>
  344 +constexpr bool cmp_less(L lhs, R rhs) noexcept {
  345 + static_assert(std::is_integral_v<L> && std::is_integral_v<R>, "magic_enum::detail::cmp_less requires integral type.");
  346 +
  347 + if constexpr (std::is_signed_v<L> == std::is_signed_v<R>) {
  348 + // If same signedness (both signed or both unsigned).
  349 + return lhs < rhs;
  350 + } else if constexpr (std::is_same_v<L, bool>) { // bool special case
  351 + return static_cast<R>(lhs) < rhs;
  352 + } else if constexpr (std::is_same_v<R, bool>) { // bool special case
  353 + return lhs < static_cast<L>(rhs);
  354 + } else if constexpr (std::is_signed_v<R>) {
  355 + // If 'right' is negative, then result is 'false', otherwise cast & compare.
  356 + return rhs > 0 && lhs < static_cast<std::make_unsigned_t<R>>(rhs);
  357 + } else {
  358 + // If 'left' is negative, then result is 'true', otherwise cast & compare.
  359 + return lhs < 0 || static_cast<std::make_unsigned_t<L>>(lhs) < rhs;
  360 + }
  361 +}
  362 +
  363 +template <typename I>
  364 +constexpr I log2(I value) noexcept {
  365 + static_assert(std::is_integral_v<I>, "magic_enum::detail::log2 requires integral type.");
  366 +
  367 + if constexpr (std::is_same_v<I, bool>) { // bool special case
  368 + return assert(false), value;
  369 + } else {
  370 + auto ret = I{0};
  371 + for (; value > I{1}; value >>= I{1}, ++ret) {}
  372 +
  373 + return ret;
  374 + }
  375 +}
  376 +
  377 +template <typename T>
  378 +inline constexpr bool is_enum_v = std::is_enum_v<T> && std::is_same_v<T, std::decay_t<T>>;
  379 +
  380 +template <typename E>
  381 +constexpr auto n() noexcept {
  382 + static_assert(is_enum_v<E>, "magic_enum::detail::n requires enum type.");
  383 +
  384 + [[maybe_unused]] constexpr auto custom = customize::enum_type_name<E>();
  385 + static_assert(std::is_same_v<std::decay_t<decltype(custom)>, customize::customize_t>, "magic_enum::customize requires customize_t type.");
  386 + if constexpr (custom.index() == 0) {
  387 + constexpr auto name = std::get<string_view>(custom);
  388 + static_assert(!name.empty(), "magic_enum::customize requires not empty string.");
  389 + return static_string<name.size()>{name};
  390 + } else if constexpr (custom.index() == 1 && supported<E>::value) {
  391 +#if defined(__clang__) || defined(__GNUC__)
  392 + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2});
  393 +#elif defined(_MSC_VER)
  394 + constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17});
  395 +#else
  396 + constexpr auto name = string_view{};
  397 +#endif
  398 + return static_string<name.size()>{name};
  399 + } else {
  400 + return static_string<0>{}; // Unsupported compiler or Invalid customize.
  401 + }
  402 +}
  403 +
  404 +template <typename E>
  405 +inline constexpr auto type_name_v = n<E>();
  406 +
  407 +template <typename E, E V>
  408 +constexpr auto n() noexcept {
  409 + static_assert(is_enum_v<E>, "magic_enum::detail::n requires enum type.");
  410 +
  411 + [[maybe_unused]] constexpr auto custom = customize::enum_name<E>(V);
  412 + static_assert(std::is_same_v<std::decay_t<decltype(custom)>, customize::customize_t>, "magic_enum::customize requires customize_t type.");
  413 + if constexpr (custom.index() == 0) {
  414 + constexpr auto name = std::get<string_view>(custom);
  415 + static_assert(!name.empty(), "magic_enum::customize requires not empty string.");
  416 + return static_string<name.size()>{name};
  417 + } else if constexpr (custom.index() == 1 && supported<E>::value) {
  418 +#if defined(__clang__) || defined(__GNUC__)
  419 + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2});
  420 +#elif defined(_MSC_VER)
  421 + constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17});
  422 +#else
  423 + constexpr auto name = string_view{};
  424 +#endif
  425 + return static_string<name.size()>{name};
  426 + } else {
  427 + return static_string<0>{}; // Unsupported compiler or Invalid customize.
  428 + }
  429 +}
  430 +
  431 +template <typename E, E V>
  432 +inline constexpr auto enum_name_v = n<E, V>();
  433 +
  434 +template <typename E, auto V>
  435 +constexpr bool is_valid() noexcept {
  436 + static_assert(is_enum_v<E>, "magic_enum::detail::is_valid requires enum type.");
  437 +
  438 + return n<E, static_cast<E>(V)>().size() != 0;
  439 +}
  440 +
  441 +template <typename E, int O, bool IsFlags, typename U = std::underlying_type_t<E>>
  442 +constexpr E value(std::size_t i) noexcept {
  443 + static_assert(is_enum_v<E>, "magic_enum::detail::value requires enum type.");
  444 +
  445 + if constexpr (std::is_same_v<U, bool>) { // bool special case
  446 + static_assert(O == 0, "magic_enum::detail::value requires valid offset.");
  447 +
  448 + return static_cast<E>(i);
  449 + } else if constexpr (IsFlags) {
  450 + return static_cast<E>(U{1} << static_cast<U>(static_cast<int>(i) + O));
  451 + } else {
  452 + return static_cast<E>(static_cast<int>(i) + O);
  453 + }
  454 +}
  455 +
  456 +template <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>
  457 +constexpr int reflected_min() noexcept {
  458 + static_assert(is_enum_v<E>, "magic_enum::detail::reflected_min requires enum type.");
  459 +
  460 + if constexpr (IsFlags) {
  461 + return 0;
  462 + } else {
  463 + constexpr auto lhs = range_min<E>::value;
  464 + constexpr auto rhs = (std::numeric_limits<U>::min)();
  465 +
  466 + if constexpr (cmp_less(rhs, lhs)) {
  467 + return lhs;
  468 + } else {
  469 + return rhs;
  470 + }
  471 + }
  472 +}
  473 +
  474 +template <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>
  475 +constexpr int reflected_max() noexcept {
  476 + static_assert(is_enum_v<E>, "magic_enum::detail::reflected_max requires enum type.");
  477 +
  478 + if constexpr (IsFlags) {
  479 + return std::numeric_limits<U>::digits - 1;
  480 + } else {
  481 + constexpr auto lhs = range_max<E>::value;
  482 + constexpr auto rhs = (std::numeric_limits<U>::max)();
  483 +
  484 + if constexpr (cmp_less(lhs, rhs)) {
  485 + return lhs;
  486 + } else {
  487 + return rhs;
  488 + }
  489 + }
  490 +}
  491 +
  492 +template <typename E, bool IsFlags>
  493 +inline constexpr auto reflected_min_v = reflected_min<E, IsFlags>();
  494 +
  495 +template <typename E, bool IsFlags>
  496 +inline constexpr auto reflected_max_v = reflected_max<E, IsFlags>();
  497 +
  498 +template <std::size_t N>
  499 +constexpr std::size_t values_count(const bool (&valid)[N]) noexcept {
  500 + auto count = std::size_t{0};
  501 + for (std::size_t i = 0; i < N; ++i) {
  502 + if (valid[i]) {
  503 + ++count;
  504 + }
  505 + }
  506 +
  507 + return count;
  508 +}
  509 +
  510 +template <typename E, bool IsFlags, int Min, std::size_t... I>
  511 +constexpr auto values(std::index_sequence<I...>) noexcept {
  512 + static_assert(is_enum_v<E>, "magic_enum::detail::values requires enum type.");
  513 + constexpr bool valid[sizeof...(I)] = {is_valid<E, value<E, Min, IsFlags>(I)>()...};
  514 + constexpr std::size_t count = values_count(valid);
  515 +
  516 + if constexpr (count > 0) {
  517 + E values[count] = {};
  518 + for (std::size_t i = 0, v = 0; v < count; ++i) {
  519 + if (valid[i]) {
  520 + values[v++] = value<E, Min, IsFlags>(i);
  521 + }
  522 + }
  523 +
  524 + return to_array(values, std::make_index_sequence<count>{});
  525 + } else {
  526 + return std::array<E, 0>{};
  527 + }
  528 +}
  529 +
  530 +template <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>
  531 +constexpr auto values() noexcept {
  532 + static_assert(is_enum_v<E>, "magic_enum::detail::values requires enum type.");
  533 + constexpr auto min = reflected_min_v<E, IsFlags>;
  534 + constexpr auto max = reflected_max_v<E, IsFlags>;
  535 + constexpr auto range_size = max - min + 1;
  536 + static_assert(range_size > 0, "magic_enum::enum_range requires valid size.");
  537 + static_assert(range_size < (std::numeric_limits<std::uint16_t>::max)(), "magic_enum::enum_range requires valid size.");
  538 +
  539 + return values<E, IsFlags, reflected_min_v<E, IsFlags>>(std::make_index_sequence<range_size>{});
  540 +}
  541 +
  542 +template <typename E, typename U = std::underlying_type_t<E>>
  543 +constexpr bool is_flags_enum() noexcept {
  544 + static_assert(is_enum_v<E>, "magic_enum::detail::is_flags_enum requires enum type.");
  545 +
  546 + if constexpr (has_is_flags<E>::value) {
  547 + return customize::enum_range<E>::is_flags;
  548 + } else if constexpr (std::is_same_v<U, bool>) { // bool special case
  549 + return false;
  550 + } else {
  551 +#if defined(MAGIC_ENUM_NO_CHECK_FLAGS)
  552 + return false;
  553 +#else
  554 + constexpr auto flags_values = values<E, true>();
  555 + constexpr auto default_values = values<E, false>();
  556 + if (flags_values.size() == 0 || default_values.size() > flags_values.size()) {
  557 + return false;
  558 + }
  559 + for (std::size_t i = 0; i < default_values.size(); ++i) {
  560 + const auto v = static_cast<U>(default_values[i]);
  561 + if (v != 0 && (v & (v - 1)) != 0) {
  562 + return false;
  563 + }
  564 + }
  565 + return flags_values.size() > 0;
  566 +#endif
  567 + }
  568 +}
  569 +
  570 +template <typename E>
  571 +inline constexpr bool is_flags_v = is_flags_enum<E>();
  572 +
  573 +template <typename E>
  574 +inline constexpr std::array values_v = values<E, is_flags_v<E>>();
  575 +
  576 +template <typename E, typename D = std::decay_t<E>>
  577 +using values_t = decltype((values_v<D>));
  578 +
  579 +template <typename E>
  580 +inline constexpr auto count_v = values_v<E>.size();
  581 +
  582 +template <typename E, typename U = std::underlying_type_t<E>>
  583 +inline constexpr auto min_v = (count_v<E> > 0) ? static_cast<U>(values_v<E>.front()) : U{0};
  584 +
  585 +template <typename E, typename U = std::underlying_type_t<E>>
  586 +inline constexpr auto max_v = (count_v<E> > 0) ? static_cast<U>(values_v<E>.back()) : U{0};
  587 +
  588 +template <typename E, std::size_t... I>
  589 +constexpr auto names(std::index_sequence<I...>) noexcept {
  590 + static_assert(is_enum_v<E>, "magic_enum::detail::names requires enum type.");
  591 +
  592 + return std::array<string_view, sizeof...(I)>{{enum_name_v<E, values_v<E>[I]>...}};
  593 +}
  594 +
  595 +template <typename E>
  596 +inline constexpr std::array names_v = names<E>(std::make_index_sequence<count_v<E>>{});
  597 +
  598 +template <typename E, typename D = std::decay_t<E>>
  599 +using names_t = decltype((names_v<D>));
  600 +
  601 +template <typename E, std::size_t... I>
  602 +constexpr auto entries(std::index_sequence<I...>) noexcept {
  603 + static_assert(is_enum_v<E>, "magic_enum::detail::entries requires enum type.");
  604 +
  605 + return std::array<std::pair<E, string_view>, sizeof...(I)>{{{values_v<E>[I], enum_name_v<E, values_v<E>[I]>}...}};
  606 +}
  607 +
  608 +template <typename E>
  609 +inline constexpr std::array entries_v = entries<E>(std::make_index_sequence<count_v<E>>{});
  610 +
  611 +template <typename E, typename D = std::decay_t<E>>
  612 +using entries_t = decltype((entries_v<D>));
  613 +
  614 +template <typename E, typename U = std::underlying_type_t<E>>
  615 +constexpr bool is_sparse() noexcept {
  616 + static_assert(is_enum_v<E>, "magic_enum::detail::is_sparse requires enum type.");
  617 +
  618 + if constexpr (count_v<E> == 0) {
  619 + return false;
  620 + } else if constexpr (std::is_same_v<U, bool>) { // bool special case
  621 + return false;
  622 + } else {
  623 + constexpr auto max = is_flags_v<E> ? log2(max_v<E>) : max_v<E>;
  624 + constexpr auto min = is_flags_v<E> ? log2(min_v<E>) : min_v<E>;
  625 + constexpr auto range_size = max - min + 1;
  626 +
  627 + return range_size != count_v<E>;
  628 + }
  629 +}
  630 +
  631 +template <typename E>
  632 +inline constexpr bool is_sparse_v = is_sparse<E>();
  633 +
  634 +template <typename E, typename U = std::underlying_type_t<E>>
  635 +constexpr U values_ors() noexcept {
  636 + static_assert(is_enum_v<E>, "magic_enum::detail::values_ors requires enum type.");
  637 +
  638 + auto ors = U{0};
  639 + for (std::size_t i = 0; i < count_v<E>; ++i) {
  640 + ors |= static_cast<U>(values_v<E>[i]);
  641 + }
  642 +
  643 + return ors;
  644 +}
  645 +
  646 +template <bool, typename R>
  647 +struct enable_if_enum {};
  648 +
  649 +template <typename R>
  650 +struct enable_if_enum<true, R> {
  651 + using type = R;
  652 + static_assert(supported<R>::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility).");
  653 +};
  654 +
  655 +template <typename T, typename R, typename BinaryPredicate = std::equal_to<>>
  656 +using enable_if_t = typename enable_if_enum<std::is_enum_v<std::decay_t<T>> && std::is_invocable_r_v<bool, BinaryPredicate, char, char>, R>::type;
  657 +
  658 +template <typename T, typename Enable = std::enable_if_t<std::is_enum_v<std::decay_t<T>>>>
  659 +using enum_concept = T;
  660 +
  661 +template <typename T, bool = std::is_enum_v<T>>
  662 +struct is_scoped_enum : std::false_type {};
  663 +
  664 +template <typename T>
  665 +struct is_scoped_enum<T, true> : std::bool_constant<!std::is_convertible_v<T, std::underlying_type_t<T>>> {};
  666 +
  667 +template <typename T, bool = std::is_enum_v<T>>
  668 +struct is_unscoped_enum : std::false_type {};
  669 +
  670 +template <typename T>
  671 +struct is_unscoped_enum<T, true> : std::bool_constant<std::is_convertible_v<T, std::underlying_type_t<T>>> {};
  672 +
  673 +template <typename T, bool = std::is_enum_v<std::decay_t<T>>>
  674 +struct underlying_type {};
  675 +
  676 +template <typename T>
  677 +struct underlying_type<T, true> : std::underlying_type<std::decay_t<T>> {};
  678 +
  679 +template <typename Value, typename = void>
  680 +struct constexpr_hash_t;
  681 +
  682 +template <typename Value>
  683 +struct constexpr_hash_t<Value, std::enable_if_t<is_enum_v<Value>>> {
  684 + constexpr auto operator()(Value value) const noexcept {
  685 + using U = typename underlying_type<Value>::type;
  686 + if constexpr (std::is_same_v<U, bool>) { // bool special case
  687 + return static_cast<std::size_t>(value);
  688 + } else {
  689 + return static_cast<U>(value);
  690 + }
  691 + }
  692 + using secondary_hash = constexpr_hash_t;
  693 +};
  694 +
  695 +template <typename Value>
  696 +struct constexpr_hash_t<Value, std::enable_if_t<std::is_same_v<Value, string_view>>> {
  697 + static constexpr std::uint32_t crc_table[256] {
  698 + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L,
  699 + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L,
  700 + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L,
  701 + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L,
  702 + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
  703 + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L,
  704 + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL,
  705 + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL,
  706 + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L,
  707 + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
  708 + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L,
  709 + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L,
  710 + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL,
  711 + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L,
  712 + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
  713 + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL,
  714 + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L,
  715 + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L,
  716 + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L,
  717 + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
  718 + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL,
  719 + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L,
  720 + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL,
  721 + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL,
  722 + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
  723 + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L,
  724 + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L,
  725 + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L,
  726 + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL,
  727 + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
  728 + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL,
  729 + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL
  730 + };
  731 + constexpr std::uint32_t operator()(string_view value) const noexcept {
  732 + auto crc = static_cast<std::uint32_t>(0xffffffffL);
  733 + for (const auto c : value) {
  734 + crc = (crc >> 8) ^ crc_table[(crc ^ static_cast<std::uint32_t>(c)) & 0xff];
  735 + }
  736 + return crc ^ 0xffffffffL;
  737 + }
  738 +
  739 + struct secondary_hash {
  740 + constexpr std::uint32_t operator()(string_view value) const noexcept {
  741 + auto acc = static_cast<std::uint64_t>(2166136261ULL);
  742 + for (const auto c : value) {
  743 + acc = ((acc ^ static_cast<std::uint64_t>(c)) * static_cast<std::uint64_t>(16777619ULL)) & (std::numeric_limits<std::uint32_t>::max)();
  744 + }
  745 + return static_cast<std::uint32_t>(acc);
  746 + }
  747 + };
  748 +};
  749 +
  750 +template <typename Hash>
  751 +constexpr static Hash hash_v{};
  752 +
  753 +template <auto* GlobValues, typename Hash>
  754 +constexpr auto calculate_cases(std::size_t Page) noexcept {
  755 + constexpr std::array values = *GlobValues;
  756 + constexpr std::size_t size = values.size();
  757 +
  758 + using switch_t = std::invoke_result_t<Hash, typename decltype(values)::value_type>;
  759 + static_assert(std::is_integral_v<switch_t> && !std::is_same_v<switch_t, bool>);
  760 + const std::size_t values_to = (std::min)(static_cast<std::size_t>(256), size - Page);
  761 +
  762 + std::array<switch_t, 256> result{};
  763 + auto fill = result.begin();
  764 + {
  765 + auto first = values.begin() + static_cast<std::ptrdiff_t>(Page);
  766 + auto last = values.begin() + static_cast<std::ptrdiff_t>(Page + values_to);
  767 + while (first != last) {
  768 + *fill++ = hash_v<Hash>(*first++);
  769 + }
  770 + }
  771 +
  772 + // dead cases, try to avoid case collisions
  773 + for (switch_t last_value = result[values_to - 1]; fill != result.end() && last_value != (std::numeric_limits<switch_t>::max)(); *fill++ = ++last_value) {
  774 + }
  775 +
  776 + {
  777 + auto it = result.begin();
  778 + auto last_value = (std::numeric_limits<switch_t>::min)();
  779 + for (; fill != result.end(); *fill++ = last_value++) {
  780 + while (last_value == *it) {
  781 + ++last_value, ++it;
  782 + }
  783 + }
  784 + }
  785 +
  786 + return result;
  787 +}
  788 +
  789 +template <typename R, typename F, typename... Args>
  790 +constexpr R invoke_r(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_r_v<R, F, Args...>) {
  791 + if constexpr (std::is_void_v<R>) {
  792 + std::forward<F>(f)(std::forward<Args>(args)...);
  793 + } else {
  794 + return static_cast<R>(std::forward<F>(f)(std::forward<Args>(args)...));
  795 + }
  796 +}
  797 +
  798 +enum class case_call_t {
  799 + index, value
  800 +};
  801 +
  802 +template <typename T = void>
  803 +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v<T>) { return T{}; };
  804 +
  805 +template <>
  806 +inline constexpr auto default_result_type_lambda<void> = []() noexcept {};
  807 +
  808 +template <auto* Arr, typename Hash>
  809 +constexpr bool no_duplicate() noexcept {
  810 + using value_t = std::decay_t<decltype((*Arr)[0])>;
  811 + using hash_value_t = std::invoke_result_t<Hash, value_t>;
  812 + std::array<hash_value_t, Arr->size()> hashes{};
  813 + std::size_t size = 0;
  814 + for (auto elem : *Arr) {
  815 + hashes[size] = hash_v<Hash>(elem);
  816 + for (auto i = size++; i > 0; --i) {
  817 + if (hashes[i] < hashes[i - 1]) {
  818 + auto tmp = hashes[i];
  819 + hashes[i] = hashes[i - 1];
  820 + hashes[i - 1] = tmp;
  821 + } else if (hashes[i] == hashes[i - 1]) {
  822 + return false;
  823 + } else {
  824 + break;
  825 + }
  826 + }
  827 + }
  828 + return true;
  829 +}
  830 +
  831 +#define MAGIC_ENUM_FOR_EACH_256(T) T(0)T(1)T(2)T(3)T(4)T(5)T(6)T(7)T(8)T(9)T(10)T(11)T(12)T(13)T(14)T(15)T(16)T(17)T(18)T(19)T(20)T(21)T(22)T(23)T(24)T(25)T(26)T(27)T(28)T(29)T(30)T(31) \
  832 + T(32)T(33)T(34)T(35)T(36)T(37)T(38)T(39)T(40)T(41)T(42)T(43)T(44)T(45)T(46)T(47)T(48)T(49)T(50)T(51)T(52)T(53)T(54)T(55)T(56)T(57)T(58)T(59)T(60)T(61)T(62)T(63) \
  833 + T(64)T(65)T(66)T(67)T(68)T(69)T(70)T(71)T(72)T(73)T(74)T(75)T(76)T(77)T(78)T(79)T(80)T(81)T(82)T(83)T(84)T(85)T(86)T(87)T(88)T(89)T(90)T(91)T(92)T(93)T(94)T(95) \
  834 + T(96)T(97)T(98)T(99)T(100)T(101)T(102)T(103)T(104)T(105)T(106)T(107)T(108)T(109)T(110)T(111)T(112)T(113)T(114)T(115)T(116)T(117)T(118)T(119)T(120)T(121)T(122)T(123)T(124)T(125)T(126)T(127) \
  835 + T(128)T(129)T(130)T(131)T(132)T(133)T(134)T(135)T(136)T(137)T(138)T(139)T(140)T(141)T(142)T(143)T(144)T(145)T(146)T(147)T(148)T(149)T(150)T(151)T(152)T(153)T(154)T(155)T(156)T(157)T(158)T(159) \
  836 + T(160)T(161)T(162)T(163)T(164)T(165)T(166)T(167)T(168)T(169)T(170)T(171)T(172)T(173)T(174)T(175)T(176)T(177)T(178)T(179)T(180)T(181)T(182)T(183)T(184)T(185)T(186)T(187)T(188)T(189)T(190)T(191) \
  837 + T(192)T(193)T(194)T(195)T(196)T(197)T(198)T(199)T(200)T(201)T(202)T(203)T(204)T(205)T(206)T(207)T(208)T(209)T(210)T(211)T(212)T(213)T(214)T(215)T(216)T(217)T(218)T(219)T(220)T(221)T(222)T(223) \
  838 + T(224)T(225)T(226)T(227)T(228)T(229)T(230)T(231)T(232)T(233)T(234)T(235)T(236)T(237)T(238)T(239)T(240)T(241)T(242)T(243)T(244)T(245)T(246)T(247)T(248)T(249)T(250)T(251)T(252)T(253)T(254)T(255)
  839 +
  840 +#define MAGIC_ENUM_CASE(val) \
  841 + case cases[val]: \
  842 + if constexpr ((val) + Page < size) { \
  843 + if (!pred(values[val + Page], searched)) { \
  844 + break; \
  845 + } \
  846 + if constexpr (CallValue == case_call_t::index) { \
  847 + if constexpr (std::is_invocable_r_v<result_t, Lambda, std::integral_constant<std::size_t, val + Page>>) { \
  848 + return detail::invoke_r<result_t>(std::forward<Lambda>(lambda), std::integral_constant<std::size_t, val + Page>{}); \
  849 + } else if constexpr (std::is_invocable_v<Lambda, std::integral_constant<std::size_t, val + Page>>) { \
  850 + assert(false && "magic_enum::detail::constexpr_switch wrong result type."); \
  851 + } \
  852 + } else if constexpr (CallValue == case_call_t::value) { \
  853 + if constexpr (std::is_invocable_r_v<result_t, Lambda, enum_constant<values[val + Page]>>) { \
  854 + return detail::invoke_r<result_t>(std::forward<Lambda>(lambda), enum_constant<values[val + Page]>{}); \
  855 + } else if constexpr (std::is_invocable_r_v<result_t, Lambda, enum_constant<values[val + Page]>>) { \
  856 + assert(false && "magic_enum::detail::constexpr_switch wrong result type."); \
  857 + } \
  858 + } \
  859 + break; \
  860 + } else [[fallthrough]];
  861 +
  862 +template <auto* GlobValues,
  863 + case_call_t CallValue,
  864 + std::size_t Page = 0,
  865 + typename Hash = constexpr_hash_t<typename std::decay_t<decltype(*GlobValues)>::value_type>,
  866 + typename Lambda, typename ResultGetterType = decltype(default_result_type_lambda<>),
  867 + typename BinaryPredicate = std::equal_to<>>
  868 +constexpr std::invoke_result_t<ResultGetterType> constexpr_switch(
  869 + Lambda&& lambda,
  870 + typename std::decay_t<decltype(*GlobValues)>::value_type searched,
  871 + ResultGetterType&& def = default_result_type_lambda<>,
  872 + BinaryPredicate&& pred = {}) {
  873 + using result_t = std::invoke_result_t<ResultGetterType>;
  874 + using hash_t = std::conditional_t<no_duplicate<GlobValues, Hash>(), Hash, typename Hash::secondary_hash>;
  875 + constexpr std::array values = *GlobValues;
  876 + constexpr std::size_t size = values.size();
  877 + constexpr std::array cases = calculate_cases<GlobValues, hash_t>(Page);
  878 +
  879 + switch (hash_v<hash_t>(searched)) {
  880 + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_CASE)
  881 + default:
  882 + if constexpr (size > 256 + Page) {
  883 + return constexpr_switch<GlobValues, CallValue, Page + 256, Hash>(std::forward<Lambda>(lambda), searched, std::forward<ResultGetterType>(def));
  884 + }
  885 + break;
  886 + }
  887 + return def();
  888 +}
  889 +
  890 +#undef MAGIC_ENUM_FOR_EACH_256
  891 +#undef MAGIC_ENUM_CASE
  892 +
  893 +template <typename E, typename Lambda, std::size_t... I>
  894 +constexpr auto for_each(Lambda&& lambda, std::index_sequence<I...>) {
  895 + static_assert(is_enum_v<E>, "magic_enum::detail::for_each requires enum type.");
  896 + constexpr bool has_void_return = (std::is_void_v<std::invoke_result_t<Lambda, enum_constant<values_v<E>[I]>>> || ...);
  897 + constexpr bool all_same_return = (std::is_same_v<std::invoke_result_t<Lambda, enum_constant<values_v<E>[0]>>, std::invoke_result_t<Lambda, enum_constant<values_v<E>[I]>>> && ...);
  898 +
  899 + if constexpr (has_void_return) {
  900 + (lambda(enum_constant<values_v<E>[I]>{}), ...);
  901 + } else if constexpr (all_same_return) {
  902 + return std::array{lambda(enum_constant<values_v<E>[I]>{})...};
  903 + } else {
  904 + return std::tuple{lambda(enum_constant<values_v<E>[I]>{})...};
  905 + }
  906 +}
  907 +
  908 +} // namespace magic_enum::detail
  909 +
  910 +// Checks is magic_enum supported compiler.
  911 +inline constexpr bool is_magic_enum_supported = detail::supported<void>::value;
  912 +
  913 +template <typename T>
  914 +using Enum = detail::enum_concept<T>;
  915 +
  916 +// Checks whether T is an Unscoped enumeration type.
  917 +// Provides the member constant value which is equal to true, if T is an [Unscoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration) type. Otherwise, value is equal to false.
  918 +template <typename T>
  919 +struct is_unscoped_enum : detail::is_unscoped_enum<T> {};
  920 +
  921 +template <typename T>
  922 +inline constexpr bool is_unscoped_enum_v = is_unscoped_enum<T>::value;
  923 +
  924 +// Checks whether T is an Scoped enumeration type.
  925 +// Provides the member constant value which is equal to true, if T is an [Scoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations) type. Otherwise, value is equal to false.
  926 +template <typename T>
  927 +struct is_scoped_enum : detail::is_scoped_enum<T> {};
  928 +
  929 +template <typename T>
  930 +inline constexpr bool is_scoped_enum_v = is_scoped_enum<T>::value;
  931 +
  932 +// If T is a complete enumeration type, provides a member typedef type that names the underlying type of T.
  933 +// Otherwise, if T is not an enumeration type, there is no member type. Otherwise (T is an incomplete enumeration type), the program is ill-formed.
  934 +template <typename T>
  935 +struct underlying_type : detail::underlying_type<T> {};
  936 +
  937 +template <typename T>
  938 +using underlying_type_t = typename underlying_type<T>::type;
  939 +
  940 +template <auto V>
  941 +using enum_constant = detail::enum_constant<V>;
  942 +
  943 +// Returns type name of enum.
  944 +template <typename E>
  945 +[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_t<E, string_view> {
  946 + constexpr string_view name = detail::type_name_v<std::decay_t<E>>;
  947 + static_assert(!name.empty(), "magic_enum::enum_type_name enum type does not have a name.");
  948 +
  949 + return name;
  950 +}
  951 +
  952 +// Returns number of enum values.
  953 +template <typename E>
  954 +[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_t<E, std::size_t> {
  955 + return detail::count_v<std::decay_t<E>>;
  956 +}
  957 +
  958 +// Returns enum value at specified index.
  959 +// No bounds checking is performed: the behavior is undefined if index >= number of enum values.
  960 +template <typename E>
  961 +[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_t<E, std::decay_t<E>> {
  962 + using D = std::decay_t<E>;
  963 +
  964 + if constexpr (detail::is_sparse_v<D>) {
  965 + return assert((index < detail::count_v<D>)), detail::values_v<D>[index];
  966 + } else {
  967 + constexpr bool is_flag = detail::is_flags_v<D>;
  968 + constexpr auto min = is_flag ? detail::log2(detail::min_v<D>) : detail::min_v<D>;
  969 +
  970 + return assert((index < detail::count_v<D>)), detail::value<D, min, is_flag>(index);
  971 + }
  972 +}
  973 +
  974 +// Returns enum value at specified index.
  975 +template <typename E, std::size_t I>
  976 +[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_t<E, std::decay_t<E>> {
  977 + using D = std::decay_t<E>;
  978 + static_assert(I < detail::count_v<D>, "magic_enum::enum_value out of range.");
  979 +
  980 + return enum_value<D>(I);
  981 +}
  982 +
  983 +// Returns std::array with enum values, sorted by enum value.
  984 +template <typename E>
  985 +[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_t<E, detail::values_t<E>> {
  986 + return detail::values_v<std::decay_t<E>>;
  987 +}
  988 +
  989 +// Returns integer value from enum value.
  990 +template <typename E>
  991 +[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_t<E, underlying_type_t<E>> {
  992 + return static_cast<underlying_type_t<E>>(value);
  993 +}
  994 +
  995 +// Returns underlying value from enum value.
  996 +template <typename E>
  997 +[[nodiscard]] constexpr auto enum_underlying(E value) noexcept -> detail::enable_if_t<E, underlying_type_t<E>> {
  998 + return static_cast<underlying_type_t<E>>(value);
  999 +}
  1000 +
  1001 +// Obtains index in enum values from enum value.
  1002 +// Returns optional with index.
  1003 +template <typename E>
  1004 +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t<E, optional<std::size_t>> {
  1005 + using D = std::decay_t<E>;
  1006 + using U = underlying_type_t<D>;
  1007 +
  1008 + if constexpr (detail::count_v<D> == 0) {
  1009 + return {}; // Empty enum.
  1010 + } else if constexpr (detail::is_sparse_v<D> || detail::is_flags_v<D>) {
  1011 + return detail::constexpr_switch<&detail::values_v<D>, detail::case_call_t::index>(
  1012 + [](std::size_t i) { return optional<std::size_t>{i}; },
  1013 + value,
  1014 + detail::default_result_type_lambda<optional<std::size_t>>);
  1015 + } else {
  1016 + const auto v = static_cast<U>(value);
  1017 + if (v >= detail::min_v<D> && v <= detail::max_v<D>) {
  1018 + return static_cast<std::size_t>(v - detail::min_v<D>);
  1019 + }
  1020 + return {}; // Invalid value or out of range.
  1021 + }
  1022 +}
  1023 +
  1024 +// Obtains index in enum values from static storage enum variable.
  1025 +template <auto V>
  1026 +[[nodiscard]] constexpr auto enum_index() noexcept -> detail::enable_if_t<decltype(V), std::size_t> {
  1027 + constexpr auto index = enum_index<std::decay_t<decltype(V)>>(V);
  1028 + static_assert(index, "magic_enum::enum_index enum value does not have a index.");
  1029 +
  1030 + return *index;
  1031 +}
  1032 +
  1033 +// Returns name from static storage enum variable.
  1034 +// This version is much lighter on the compile times and is not restricted to the enum_range limitation.
  1035 +template <auto V>
  1036 +[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_t<decltype(V), string_view> {
  1037 + constexpr string_view name = detail::enum_name_v<std::decay_t<decltype(V)>, V>;
  1038 + static_assert(!name.empty(), "magic_enum::enum_name enum value does not have a name.");
  1039 +
  1040 + return name;
  1041 +}
  1042 +
  1043 +// Returns name from enum value.
  1044 +// If enum value does not have name or value out of range, returns empty string.
  1045 +template <typename E>
  1046 +[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_t<E, string_view> {
  1047 + using D = std::decay_t<E>;
  1048 +
  1049 + if (const auto i = enum_index<D>(value)) {
  1050 + return detail::names_v<D>[*i];
  1051 + }
  1052 + return {};
  1053 +}
  1054 +
  1055 +// Returns name from enum-flags value.
  1056 +// If enum-flags value does not have name or value out of range, returns empty string.
  1057 +template <typename E>
  1058 +[[nodiscard]] auto enum_flags_name(E value) -> detail::enable_if_t<E, string> {
  1059 + using D = std::decay_t<E>;
  1060 + using U = underlying_type_t<D>;
  1061 +
  1062 + if constexpr (detail::is_flags_v<D>) {
  1063 + string name;
  1064 + auto check_value = U{0};
  1065 + for (std::size_t i = 0; i < detail::count_v<D>; ++i) {
  1066 + if (const auto v = static_cast<U>(enum_value<D>(i)); (static_cast<U>(value) & v) != 0) {
  1067 + check_value |= v;
  1068 + const auto n = detail::names_v<D>[i];
  1069 + if (!name.empty()) {
  1070 + name.append(1, '|');
  1071 + }
  1072 + name.append(n.data(), n.size());
  1073 + }
  1074 + }
  1075 +
  1076 + if (check_value != 0 && check_value == static_cast<U>(value)) {
  1077 + return name;
  1078 + }
  1079 +
  1080 + return {}; // Invalid value or out of range.
  1081 + } else {
  1082 + return string{enum_name<D>(value)};
  1083 + }
  1084 +}
  1085 +
  1086 +// Returns std::array with names, sorted by enum value.
  1087 +template <typename E>
  1088 +[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_t<E, detail::names_t<E>> {
  1089 + return detail::names_v<std::decay_t<E>>;
  1090 +}
  1091 +
  1092 +// Returns std::array with pairs (value, name), sorted by enum value.
  1093 +template <typename E>
  1094 +[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_t<E, detail::entries_t<E>> {
  1095 + return detail::entries_v<std::decay_t<E>>;
  1096 +}
  1097 +
  1098 +// Allows you to write magic_enum::enum_cast<foo>("bar", magic_enum::case_insensitive);
  1099 +inline constexpr auto case_insensitive = detail::case_insensitive{};
  1100 +
  1101 +// Obtains enum value from integer value.
  1102 +// Returns optional with enum value.
  1103 +template <typename E>
  1104 +[[nodiscard]] constexpr auto enum_cast(underlying_type_t<E> value) noexcept -> detail::enable_if_t<E, optional<std::decay_t<E>>> {
  1105 + using D = std::decay_t<E>;
  1106 + using U = underlying_type_t<D>;
  1107 +
  1108 + if constexpr (detail::count_v<D> == 0) {
  1109 + return {}; // Empty enum.
  1110 + } else if constexpr (detail::is_sparse_v<D>) {
  1111 + if constexpr (detail::is_flags_v<D>) {
  1112 + constexpr auto count = detail::count_v<D>;
  1113 + auto check_value = U{0};
  1114 + for (std::size_t i = 0; i < count; ++i) {
  1115 + if (const auto v = static_cast<U>(enum_value<D>(i)); (value & v) != 0) {
  1116 + check_value |= v;
  1117 + }
  1118 + }
  1119 +
  1120 + if (check_value != 0 && check_value == value) {
  1121 + return static_cast<D>(value);
  1122 + }
  1123 + return {}; // Invalid value or out of range.
  1124 + } else {
  1125 + return detail::constexpr_switch<&detail::values_v<D>, detail::case_call_t::value>(
  1126 + [](D v) { return optional<D>{v}; },
  1127 + static_cast<D>(value),
  1128 + detail::default_result_type_lambda<optional<D>>);
  1129 + }
  1130 + } else {
  1131 + constexpr auto min = detail::min_v<D>;
  1132 + constexpr auto max = detail::is_flags_v<D> ? detail::values_ors<D>() : detail::max_v<D>;
  1133 +
  1134 + if (value >= min && value <= max) {
  1135 + return static_cast<D>(value);
  1136 + }
  1137 + return {}; // Invalid value or out of range.
  1138 + }
  1139 +}
  1140 +
  1141 +// Obtains enum value from name.
  1142 +// Returns optional with enum value.
  1143 +template <typename E, typename BinaryPredicate = std::equal_to<>>
  1144 +[[nodiscard]] constexpr auto enum_cast(string_view value, [[maybe_unused]] BinaryPredicate&& p = {}) noexcept(detail::is_nothrow_invocable<BinaryPredicate>()) -> detail::enable_if_t<E, optional<std::decay_t<E>>, BinaryPredicate> {
  1145 + static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::enum_cast requires bool(char, char) invocable predicate.");
  1146 + using D = std::decay_t<E>;
  1147 + using U = underlying_type_t<D>;
  1148 +
  1149 + if constexpr (detail::count_v<D> == 0) {
  1150 + return {}; // Empty enum.
  1151 + } else if constexpr (detail::is_flags_v<D>) {
  1152 + auto result = U{0};
  1153 + while (!value.empty()) {
  1154 + const auto d = detail::find(value, '|');
  1155 + const auto s = (d == string_view::npos) ? value : value.substr(0, d);
  1156 + auto f = U{0};
  1157 + for (std::size_t i = 0; i < detail::count_v<D>; ++i) {
  1158 + if (detail::cmp_equal(s, detail::names_v<D>[i], p)) {
  1159 + f = static_cast<U>(enum_value<D>(i));
  1160 + result |= f;
  1161 + break;
  1162 + }
  1163 + }
  1164 + if (f == U{0}) {
  1165 + return {}; // Invalid value or out of range.
  1166 + }
  1167 + value.remove_prefix((d == string_view::npos) ? value.size() : d + 1);
  1168 + }
  1169 +
  1170 + if (result != U{0}) {
  1171 + return static_cast<D>(result);
  1172 + }
  1173 + return {}; // Invalid value or out of range.
  1174 + } else if constexpr (detail::count_v<D> > 0) {
  1175 + if constexpr (detail::is_default_predicate<BinaryPredicate>()) {
  1176 + return detail::constexpr_switch<&detail::names_v<D>, detail::case_call_t::index>(
  1177 + [](std::size_t i) { return optional<D>{detail::values_v<D>[i]}; },
  1178 + value,
  1179 + detail::default_result_type_lambda<optional<D>>,
  1180 + [&p](string_view lhs, string_view rhs) { return detail::cmp_equal(lhs, rhs, p); });
  1181 + } else {
  1182 + for (std::size_t i = 0; i < detail::count_v<D>; ++i) {
  1183 + if (detail::cmp_equal(value, detail::names_v<D>[i], p)) {
  1184 + return enum_value<D>(i);
  1185 + }
  1186 + }
  1187 + return {}; // Invalid value or out of range.
  1188 + }
  1189 + }
  1190 +}
  1191 +
  1192 +// Checks whether enum contains enumerator with such enum value.
  1193 +template <typename E>
  1194 +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t<E, bool> {
  1195 + using D = std::decay_t<E>;
  1196 + using U = underlying_type_t<D>;
  1197 +
  1198 + return static_cast<bool>(enum_cast<D>(static_cast<U>(value)));
  1199 +}
  1200 +
  1201 +// Checks whether enum contains enumerator with such integer value.
  1202 +template <typename E>
  1203 +[[nodiscard]] constexpr auto enum_contains(underlying_type_t<E> value) noexcept -> detail::enable_if_t<E, bool> {
  1204 + using D = std::decay_t<E>;
  1205 +
  1206 + return static_cast<bool>(enum_cast<D>(value));
  1207 +}
  1208 +
  1209 +// Checks whether enum contains enumerator with such name.
  1210 +template <typename E, typename BinaryPredicate = std::equal_to<>>
  1211 +[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate&& p = {}) noexcept(detail::is_nothrow_invocable<BinaryPredicate>()) -> detail::enable_if_t<E, bool, BinaryPredicate> {
  1212 + static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::enum_contains requires bool(char, char) invocable predicate.");
  1213 + using D = std::decay_t<E>;
  1214 +
  1215 + return static_cast<bool>(enum_cast<D>(value, std::forward<BinaryPredicate>(p)));
  1216 +}
  1217 +
  1218 +template <typename Result = void, typename E, typename Lambda>
  1219 +constexpr auto enum_switch(Lambda&& lambda, E value) -> detail::enable_if_t<E, Result> {
  1220 + using D = std::decay_t<E>;
  1221 +
  1222 + return detail::constexpr_switch<&detail::values_v<D>, detail::case_call_t::value>(
  1223 + std::forward<Lambda>(lambda),
  1224 + value,
  1225 + detail::default_result_type_lambda<Result>);
  1226 +}
  1227 +
  1228 +template <typename Result, typename E, typename Lambda>
  1229 +constexpr auto enum_switch(Lambda&& lambda, E value, Result&& result) -> detail::enable_if_t<E, Result> {
  1230 + using D = std::decay_t<E>;
  1231 +
  1232 + return detail::constexpr_switch<&detail::values_v<D>, detail::case_call_t::value>(
  1233 + std::forward<Lambda>(lambda),
  1234 + value,
  1235 + [&result] { return std::forward<Result>(result); });
  1236 +}
  1237 +
  1238 +template <typename E, typename Result = void, typename BinaryPredicate = std::equal_to<>, typename Lambda>
  1239 +constexpr auto enum_switch(Lambda&& lambda, string_view name, BinaryPredicate&& p = {}) -> detail::enable_if_t<E, Result, BinaryPredicate> {
  1240 + static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::enum_switch requires bool(char, char) invocable predicate.");
  1241 + using D = std::decay_t<E>;
  1242 +
  1243 + if (const auto v = enum_cast<D>(name, std::forward<BinaryPredicate>(p))) {
  1244 + return enum_switch<Result, D>(std::forward<Lambda>(lambda), *v);
  1245 + }
  1246 + return detail::default_result_type_lambda<Result>();
  1247 +}
  1248 +
  1249 +template <typename E, typename Result, typename BinaryPredicate = std::equal_to<>, typename Lambda>
  1250 +constexpr auto enum_switch(Lambda&& lambda, string_view name, Result&& result, BinaryPredicate&& p = {}) -> detail::enable_if_t<E, Result, BinaryPredicate> {
  1251 + static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::enum_switch requires bool(char, char) invocable predicate.");
  1252 + using D = std::decay_t<E>;
  1253 +
  1254 + if (const auto v = enum_cast<D>(name, std::forward<BinaryPredicate>(p))) {
  1255 + return enum_switch<Result, D>(std::forward<Lambda>(lambda), *v, std::forward<Result>(result));
  1256 + }
  1257 + return std::forward<Result>(result);
  1258 +}
  1259 +
  1260 +template <typename E, typename Result = void, typename Lambda>
  1261 +constexpr auto enum_switch(Lambda&& lambda, underlying_type_t<E> value) -> detail::enable_if_t<E, Result> {
  1262 + using D = std::decay_t<E>;
  1263 +
  1264 + if (const auto v = enum_cast<D>(value)) {
  1265 + return enum_switch<Result, D>(std::forward<Lambda>(lambda), *v);
  1266 + }
  1267 + return detail::default_result_type_lambda<Result>();
  1268 +}
  1269 +
  1270 +template <typename E, typename Result, typename Lambda>
  1271 +constexpr auto enum_switch(Lambda&& lambda, underlying_type_t<E> value, Result&& result) -> detail::enable_if_t<E, Result> {
  1272 + using D = std::decay_t<E>;
  1273 +
  1274 + if (const auto v = enum_cast<D>(value)) {
  1275 + return enum_switch<Result, D>(std::forward<Lambda>(lambda), *v, std::forward<Result>(result));
  1276 + }
  1277 + return std::forward<Result>(result);
  1278 +}
  1279 +
  1280 +template <typename E, typename Lambda>
  1281 +constexpr auto enum_for_each(Lambda&& lambda) {
  1282 + using D = std::decay_t<E>;
  1283 + static_assert(std::is_enum_v<D>, "magic_enum::enum_for_each requires enum type.");
  1284 +
  1285 + return detail::for_each<D>(std::forward<Lambda>(lambda), std::make_index_sequence<detail::count_v<D>>{});
  1286 +}
  1287 +
  1288 +namespace ostream_operators {
  1289 +
  1290 +template <typename Char, typename Traits, typename E, detail::enable_if_t<E, int> = 0>
  1291 +std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, E value) {
  1292 + using D = std::decay_t<E>;
  1293 + using U = underlying_type_t<D>;
  1294 +
  1295 + if constexpr (detail::supported<D>::value) {
  1296 + if (const auto name = enum_flags_name<D>(value); !name.empty()) {
  1297 + for (const auto c : name) {
  1298 + os.put(c);
  1299 + }
  1300 + return os;
  1301 + }
  1302 + }
  1303 + return (os << static_cast<U>(value));
  1304 +}
  1305 +
  1306 +template <typename Char, typename Traits, typename E, detail::enable_if_t<E, int> = 0>
  1307 +std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, optional<E> value) {
  1308 + return value ? (os << *value) : os;
  1309 +}
  1310 +
  1311 +} // namespace magic_enum::ostream_operators
  1312 +
  1313 +namespace istream_operators {
  1314 +
  1315 +template <typename Char, typename Traits, typename E, detail::enable_if_t<E, int> = 0>
  1316 +std::basic_istream<Char, Traits>& operator>>(std::basic_istream<Char, Traits>& is, E& value) {
  1317 + using D = std::decay_t<E>;
  1318 +
  1319 + std::basic_string<Char, Traits> s;
  1320 + is >> s;
  1321 + if (const auto v = enum_cast<D>(s)) {
  1322 + value = *v;
  1323 + } else {
  1324 + is.setstate(std::basic_ios<Char>::failbit);
  1325 + }
  1326 + return is;
  1327 +}
  1328 +
  1329 +} // namespace magic_enum::istream_operators
  1330 +
  1331 +namespace iostream_operators {
  1332 +
  1333 +using namespace ostream_operators;
  1334 +using namespace istream_operators;
  1335 +
  1336 +} // namespace magic_enum::iostream_operators
  1337 +
  1338 +namespace bitwise_operators {
  1339 +
  1340 +template <typename E, detail::enable_if_t<E, int> = 0>
  1341 +constexpr E operator~(E rhs) noexcept {
  1342 + return static_cast<E>(~static_cast<underlying_type_t<E>>(rhs));
  1343 +}
  1344 +
  1345 +template <typename E, detail::enable_if_t<E, int> = 0>
  1346 +constexpr E operator|(E lhs, E rhs) noexcept {
  1347 + return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) | static_cast<underlying_type_t<E>>(rhs));
  1348 +}
  1349 +
  1350 +template <typename E, detail::enable_if_t<E, int> = 0>
  1351 +constexpr E operator&(E lhs, E rhs) noexcept {
  1352 + return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) & static_cast<underlying_type_t<E>>(rhs));
  1353 +}
  1354 +
  1355 +template <typename E, detail::enable_if_t<E, int> = 0>
  1356 +constexpr E operator^(E lhs, E rhs) noexcept {
  1357 + return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) ^ static_cast<underlying_type_t<E>>(rhs));
  1358 +}
  1359 +
  1360 +template <typename E, detail::enable_if_t<E, int> = 0>
  1361 +constexpr E& operator|=(E& lhs, E rhs) noexcept {
  1362 + return lhs = (lhs | rhs);
  1363 +}
  1364 +
  1365 +template <typename E, detail::enable_if_t<E, int> = 0>
  1366 +constexpr E& operator&=(E& lhs, E rhs) noexcept {
  1367 + return lhs = (lhs & rhs);
  1368 +}
  1369 +
  1370 +template <typename E, detail::enable_if_t<E, int> = 0>
  1371 +constexpr E& operator^=(E& lhs, E rhs) noexcept {
  1372 + return lhs = (lhs ^ rhs);
  1373 +}
  1374 +
  1375 +} // namespace magic_enum::bitwise_operators
  1376 +
  1377 +} // namespace magic_enum
  1378 +
  1379 +#if defined(__clang__)
  1380 +# pragma clang diagnostic pop
  1381 +#elif defined(__GNUC__)
  1382 +# pragma GCC diagnostic pop
  1383 +#elif defined(_MSC_VER)
  1384 +# pragma warning(pop)
  1385 +#endif
  1386 +
  1387 +#endif // NEARGYE_MAGIC_ENUM_HPP
... ...
vendor/magic_enum/include/magic_enum_format.hpp 0 → 100644
  1 +// __ __ _ ______ _____
  2 +// | \/ | (_) | ____| / ____|_ _
  3 +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_
  4 +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _|
  5 +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_|
  6 +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____|
  7 +// __/ | https://github.com/Neargye/magic_enum
  8 +// |___/ version 0.8.1
  9 +//
  10 +// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
  11 +// SPDX-License-Identifier: MIT
  12 +// Copyright (c) 2019 - 2022 Daniil Goncharov <neargye@gmail.com>.
  13 +//
  14 +// Permission is hereby granted, free of charge, to any person obtaining a copy
  15 +// of this software and associated documentation files (the "Software"), to deal
  16 +// in the Software without restriction, including without limitation the rights
  17 +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  18 +// copies of the Software, and to permit persons to whom the Software is
  19 +// furnished to do so, subject to the following conditions:
  20 +//
  21 +// The above copyright notice and this permission notice shall be included in all
  22 +// copies or substantial portions of the Software.
  23 +//
  24 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  25 +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  26 +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  27 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  28 +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  29 +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  30 +// SOFTWARE.
  31 +
  32 +#ifndef NEARGYE_MAGIC_ENUM_FORMAT_HPP
  33 +#define NEARGYE_MAGIC_ENUM_FORMAT_HPP
  34 +
  35 +#if !defined(__cpp_lib_format)
  36 +# error "Format is not supported"
  37 +#endif
  38 +
  39 +#include "magic_enum.hpp"
  40 +
  41 +#if !defined(MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT)
  42 +# define MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT true
  43 +# define MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE
  44 +#endif // MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT
  45 +
  46 +namespace magic_enum::customize {
  47 + // customize enum to enable/disable automatic std::format
  48 + template <typename E>
  49 + constexpr bool enum_format_enabled() noexcept {
  50 + return MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT;
  51 + }
  52 +} // magic_enum::customize
  53 +
  54 +#include <format>
  55 +
  56 +template <typename E>
  57 +struct std::formatter<E, std::enable_if_t<std::is_enum_v<E> && magic_enum::customize::enum_format_enabled<E>(), char>> : std::formatter<std::string_view, char> {
  58 + auto format(E e, format_context& ctx) {
  59 + using D = std::decay_t<E>;
  60 + if constexpr (magic_enum::detail::is_flags_v<D>) {
  61 + if (auto name = magic_enum::enum_flags_name<D>(e); !name.empty()) {
  62 + return this->std::formatter<std::string_view, char>::format(std::string_view{name.data(), name.size()}, ctx);
  63 + }
  64 + } else {
  65 + if (auto name = magic_enum::enum_name<D>(e); !name.empty()) {
  66 + return this->std::formatter<std::string_view, char>::format(std::string_view{name.data(), name.size()}, ctx);
  67 + }
  68 + }
  69 + constexpr auto type_name = magic_enum::enum_type_name<E>();
  70 + throw std::format_error("Type of " + std::string{type_name.data(), type_name.size()} + " enum value: " + std::to_string(magic_enum::enum_integer<D>(e)) + " is not exists.");
  71 + }
  72 +};
  73 +
  74 +#if defined(MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE)
  75 +# undef MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT
  76 +# undef MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE
  77 +#endif // MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE
  78 +
  79 +#endif // NEARGYE_MAGIC_ENUM_FORMAT_HPP
... ...
vendor/magic_enum/include/magic_enum_fuse.hpp 0 → 100644
  1 +// __ __ _ ______ _____
  2 +// | \/ | (_) | ____| / ____|_ _
  3 +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_
  4 +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _|
  5 +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_|
  6 +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____|
  7 +// __/ | https://github.com/Neargye/magic_enum
  8 +// |___/ version 0.8.1
  9 +//
  10 +// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
  11 +// SPDX-License-Identifier: MIT
  12 +// Copyright (c) 2019 - 2022 Daniil Goncharov <neargye@gmail.com>.
  13 +//
  14 +// Permission is hereby granted, free of charge, to any person obtaining a copy
  15 +// of this software and associated documentation files (the "Software"), to deal
  16 +// in the Software without restriction, including without limitation the rights
  17 +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  18 +// copies of the Software, and to permit persons to whom the Software is
  19 +// furnished to do so, subject to the following conditions:
  20 +//
  21 +// The above copyright notice and this permission notice shall be included in all
  22 +// copies or substantial portions of the Software.
  23 +//
  24 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  25 +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  26 +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  27 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  28 +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  29 +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  30 +// SOFTWARE.
  31 +
  32 +#ifndef NEARGYE_MAGIC_ENUM_FUSE_HPP
  33 +#define NEARGYE_MAGIC_ENUM_FUSE_HPP
  34 +
  35 +#include "magic_enum.hpp"
  36 +
  37 +namespace magic_enum {
  38 +
  39 +namespace detail {
  40 +
  41 +template <typename E>
  42 +constexpr optional<std::uintmax_t> fuse_one_enum(optional<std::uintmax_t> hash, E value) noexcept {
  43 + if (hash) {
  44 + if (const auto index = enum_index(value)) {
  45 + return (*hash << log2(enum_count<E>() + 1)) | *index;
  46 + }
  47 + }
  48 + return {};
  49 +}
  50 +
  51 +template <typename E>
  52 +constexpr optional<std::uintmax_t> fuse_enum(E value) noexcept {
  53 + return fuse_one_enum(0, value);
  54 +}
  55 +
  56 +template <typename E, typename... Es>
  57 +constexpr optional<std::uintmax_t> fuse_enum(E head, Es... tail) noexcept {
  58 + return fuse_one_enum(fuse_enum(tail...), head);
  59 +}
  60 +
  61 +template <typename... Es>
  62 +constexpr auto typesafe_fuse_enum(Es... values) noexcept {
  63 + enum class enum_fuse_t : std::uintmax_t;
  64 + const auto fuse = fuse_enum(values...);
  65 + if (fuse) {
  66 + return optional<enum_fuse_t>{static_cast<enum_fuse_t>(*fuse)};
  67 + }
  68 + return optional<enum_fuse_t>{};
  69 +}
  70 +
  71 +} // namespace magic_enum::detail
  72 +
  73 +// Returns a bijective mix of several enum values. This can be used to emulate 2D switch/case statements.
  74 +template <typename... Es>
  75 +[[nodiscard]] constexpr auto enum_fuse(Es... values) noexcept {
  76 + static_assert((std::is_enum_v<std::decay_t<Es>> && ...), "magic_enum::enum_fuse requires enum type.");
  77 + static_assert(sizeof...(Es) >= 2, "magic_enum::enum_fuse requires at least 2 values.");
  78 + static_assert((detail::log2(enum_count<std::decay_t<Es>>() + 1) + ...) <= (sizeof(std::uintmax_t) * 8), "magic_enum::enum_fuse does not work for large enums");
  79 +#if defined(MAGIC_ENUM_NO_TYPESAFE_ENUM_FUSE)
  80 + const auto fuse = detail::fuse_enum<std::decay_t<Es>...>(values...);
  81 +#else
  82 + const auto fuse = detail::typesafe_fuse_enum<std::decay_t<Es>...>(values...);
  83 +#endif
  84 + return assert(fuse), fuse;
  85 +}
  86 +
  87 +} // namespace magic_enum
  88 +
  89 +#endif // NEARGYE_MAGIC_ENUM_FUSE_HPP
... ...