Skip to content

Instantly share code, notes, and snippets.

@vladiant
Created November 17, 2021 18:31
Show Gist options
  • Save vladiant/3d6d04f2535ef1ff691129118e5d2842 to your computer and use it in GitHub Desktop.
Save vladiant/3d6d04f2535ef1ff691129118e5d2842 to your computer and use it in GitHub Desktop.
Compile time checking user defined literals
// 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