Created
November 17, 2021 18:31
-
-
Save vladiant/3d6d04f2535ef1ff691129118e5d2842 to your computer and use it in GitHub Desktop.
Compile time checking user defined literals
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// With friendly permission of BENOCS GmbH (www.benocs.com) | |
// https://www.youtube.com/watch?v=KmoTKz95Wsg | |
// https://godbolt.org/z/73vT1bE86 | |
#include <cstdio> // Needed for the print-function only! | |
#include <cstdint> | |
#include <variant> | |
#include <system_error> | |
/// Simplified IPv4 address type | |
struct IPv4Address | |
{ | |
std::uint32_t addr = 0; | |
}; | |
/// Parses and extracts a decimal number from the given string | |
constexpr auto extract_dec_number(const char* str, unsigned long length, | |
unsigned long& start_index, | |
std::uint64_t max) | |
noexcept | |
-> std::variant<std::uint64_t, std::errc> | |
{ | |
constexpr auto is_digit = [](char c) { return '0' <= c && c <= '9'; }; | |
constexpr auto get_digit = [](char c) -> std::uint8_t { return c - '0'; }; | |
// Parse number. | |
std::uint64_t num = 0; // Will hold the parsed number. | |
auto old = num; // For detecting overflow. | |
auto index = start_index; | |
for (char c = 0; index < length && num <= max && old <= num; ++index) | |
{ | |
c = str[index]; | |
if (! is_digit(c)) break; | |
old = num; | |
num = num * 10 + get_digit(c); | |
} | |
// No digit parsed at all? | |
if (start_index == index) | |
return std::errc::result_out_of_range; | |
// Parsed number is too large? | |
if (num > max) | |
return std::errc::value_too_large; | |
// Overflow occurred? | |
if (old > num) | |
return std::errc::value_too_large; | |
// Return new index and extracted number. | |
start_index = index; | |
return num; | |
} | |
/// Parses and extracts an IPv4 address from the given string | |
#if __cpp_consteval >= 201811 | |
consteval | |
#else | |
constexpr | |
#endif | |
auto extract_ipv4_address(const char* str, unsigned long length, | |
unsigned long& start_index) | |
-> std::uint32_t | |
{ | |
constexpr auto is_dot = [](char c) { return '.' == c; }; | |
constexpr auto is_digit = [](char c) { return '0' <= c && c <= '9'; }; | |
// Short-circuit if nothing to parse. | |
if (start_index == length) | |
throw "Invalid IPv4 address. [Reason: Empty string-literal]"; | |
// Starts with something different than a digit? | |
if (! is_digit( str[start_index] )) | |
throw "Invalid IPv4 address. [Reason: Illegal token]"; | |
std::uint32_t addr = 0; // Will hold the parsed IPv4 address. | |
auto index = start_index; | |
unsigned long group_count = 0; | |
for (; group_count < 4 && index < length; ++group_count) | |
{ | |
// Groups not separated by '.'? | |
if (group_count != 0 && ! is_dot( str[index++] )) | |
throw "Invalid IPv4 address. [Reason: Illegal token]"; | |
// Parse next group. | |
auto old_index = index; | |
auto group = extract_dec_number(str, length, index, 255u); | |
// Parse error? | |
if (std::holds_alternative<std::errc>(group)) | |
{ | |
switch (std::get<std::errc>(group)) | |
{ | |
case std::errc::value_too_large: | |
throw "Invalid IPv4 address. [Reason: Invalid number in group]"; | |
case std::errc::result_out_of_range: | |
throw "Invalid IPv4 address. [Reason: Missing number in group]"; | |
default: | |
throw "Invalid IPv4 address."; | |
} | |
} | |
if (index - old_index > 3) // Maximal 3 digits per group! | |
throw "Invalid IPv4 address. [Reason: Too many leading zeros in group]"; | |
// Store group. | |
addr = (addr << 8) | static_cast<std::uint32_t>(std::get<0>(group)); | |
} | |
// Parsed all 4 groups of the IPv4 address? | |
if (group_count != 4) | |
throw "Invalid IPv4 address. [Reason: Too few groups]"; | |
// Return new index and extracted IPv4 address. | |
start_index = index; | |
return addr; | |
} | |
/// A literal operator for user-defined literal representing an IPv4 address type | |
#if __cpp_consteval >= 201811 | |
consteval | |
#else | |
constexpr | |
#endif | |
IPv4Address operator "" _ipv4(const char* str, std::size_t length) | |
{ | |
unsigned long index = 0; | |
auto addr = extract_ipv4_address(str, length, index); | |
if (index != length) | |
throw "Invalid IPv4 address. [Reason: Additional tokens at end]"; | |
return IPv4Address{ addr }; | |
} | |
void print(IPv4Address ip) | |
{ | |
std::printf( "%d.%d.%d.%d\n", ((ip.addr >> 24) & 0xff) | |
, ((ip.addr >> 16) & 0xff) | |
, ((ip.addr >> 8) & 0xff) | |
, ((ip.addr >> 0) & 0xff) ); | |
} | |
int main() | |
{ | |
print( "192.168.0.1"_ipv4 ); | |
try { | |
print( "192.168.0.256"_ipv4 ); | |
} | |
catch(const char* error_msg) { std::printf("%s\n", error_msg); } | |
try { | |
print( "192.168.0."_ipv4 ); | |
} | |
catch(const char* error_msg) { std::printf("%s\n", error_msg); } | |
try { | |
print( "192.168.0"_ipv4 ); | |
} | |
catch(const char* error_msg) { std::printf("%s\n", error_msg); } | |
try { | |
print( "192.168.0.1.123"_ipv4 ); | |
} | |
catch(const char* error_msg) { std::printf("%s\n", error_msg); } | |
try { | |
print( ""_ipv4 ); | |
} | |
catch(const char* error_msg) { std::printf("%s\n", error_msg); } | |
try { | |
print( "192.0168.0.1"_ipv4 ); | |
} | |
catch(const char* error_msg) { std::printf("%s\n", error_msg); } | |
// Important: | |
// In order to guarantee compile-time evaluation of constexpr, it | |
// must be called from a compile-time context, e.g. a static_assert! | |
// Note: | |
// 192 168 0 1 | |
// <==> 0xc0 0xa8 0x0 0x1 | |
/* | |
static_assert(("192.168.0.1"_ipv4).addr == 0xc0a80001); | |
static_assert(("192.168.0.256"_ipv4).addr == 0xc0a80001); | |
static_assert(("192.168.0."_ipv4).addr == 0xc0a80001); | |
static_assert(("192.168.0"_ipv4).addr == 0xc0a80001); | |
static_assert(("192.168.0.1.123"_ipv4).addr == 0xc0a80001); | |
static_assert((""_ipv4).addr == 0x0); | |
static_assert(("192.0168.0.1"_ipv4).addr == 0xc0a80001); | |
*/ | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment