Last active
August 10, 2024 06:24
-
-
Save AlexBAV/b58e92d7632bae5d6f5947be455f796f to your computer and use it in GitHub Desktop.
Constexpr GUID parsing (parsing string GUIDs at compile-time)
This file contains 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
//------------------------------------------------------------------------------------------------------- | |
// constexpr GUID parsing | |
// Written by Alexander Bessonov | |
// | |
// Licensed under the MIT license. | |
//------------------------------------------------------------------------------------------------------- | |
#pragma once | |
#include <stdexcept> | |
#include <string> | |
#include <cassert> | |
#include <cstdint> | |
#if !defined(GUID_DEFINED) | |
#define GUID_DEFINED | |
struct GUID { | |
uint32_t Data1; | |
uint16_t Data2; | |
uint16_t Data3; | |
uint8_t Data4[8]; | |
}; | |
#endif | |
namespace guid_parse | |
{ | |
namespace details | |
{ | |
constexpr const size_t short_guid_form_length = 36; // XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX | |
constexpr const size_t long_guid_form_length = 38; // {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} | |
// | |
constexpr int parse_hex_digit(const char c) | |
{ | |
using namespace std::string_literals; | |
if ('0' <= c && c <= '9') | |
return c - '0'; | |
else if ('a' <= c && c <= 'f') | |
return 10 + c - 'a'; | |
else if ('A' <= c && c <= 'F') | |
return 10 + c - 'A'; | |
else | |
throw std::domain_error{ "invalid character in GUID"s }; | |
} | |
template<class T> | |
constexpr T parse_hex(const char *ptr) | |
{ | |
constexpr size_t digits = sizeof(T) * 2; | |
T result{}; | |
for (size_t i = 0; i < digits; ++i) | |
result |= parse_hex_digit(ptr[i]) << (4 * (digits - i - 1)); | |
return result; | |
} | |
constexpr GUID make_guid_helper(const char *begin) | |
{ | |
GUID result{}; | |
result.Data1 = parse_hex<uint32_t>(begin); | |
begin += 8 + 1; | |
result.Data2 = parse_hex<uint16_t>(begin); | |
begin += 4 + 1; | |
result.Data3 = parse_hex<uint16_t>(begin); | |
begin += 4 + 1; | |
result.Data4[0] = parse_hex<uint8_t>(begin); | |
begin += 2; | |
result.Data4[1] = parse_hex<uint8_t>(begin); | |
begin += 2 + 1; | |
for (size_t i = 0; i < 6; ++i) | |
result.Data4[i + 2] = parse_hex<uint8_t>(begin + i * 2); | |
return result; | |
} | |
template<size_t N> | |
constexpr GUID make_guid(const char(&str)[N]) | |
{ | |
using namespace std::string_literals; | |
static_assert(N == (long_guid_form_length + 1) || N == (short_guid_form_length + 1), "String GUID of the form {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} or XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX is expected"); | |
if constexpr(N == (long_guid_form_length + 1)) | |
{ | |
if (str[0] != '{' || str[long_guid_form_length - 1] != '}') | |
throw std::domain_error{ "Missing opening or closing brace"s }; | |
} | |
return make_guid_helper(str + (N == (long_guid_form_length + 1) ? 1 : 0)); | |
} | |
} | |
using details::make_guid; | |
namespace literals | |
{ | |
constexpr GUID operator "" _guid(const char *str, size_t N) | |
{ | |
using namespace std::string_literals; | |
using namespace details; | |
if (!(N == long_guid_form_length || N == short_guid_form_length)) | |
throw std::domain_error{ "String GUID of the form {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} or XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX is expected"s }; | |
if (N == long_guid_form_length && (str[0] != '{' || str[long_guid_form_length - 1] != '}')) | |
throw std::domain_error{ "Missing opening or closing brace"s }; | |
return make_guid_helper(str + (N == long_guid_form_length ? 1 : 0)); | |
} | |
} | |
} |
Very nice. I used it to make a C++11 constexpr version (https://github.com/tobias-loew/constexpr-GUID-C-11-)
I also added a make_uuid and ""_uuid to create boost-uuids. Do you have any objections if I propose this to the boost uuid library?
I'm concerned by the amount of hardcoded magic integers, and multiple 36 - 39. This looks bad. DRY.
also validation is missing '-' can be replaced with anything, but still nice :)
I've implemented another one with shorter implementation
#pragma once
#include <cstdint>
#include <stdexcept>
struct guid_t {
uint8_t Data[16];
};
namespace ct_guid {
using namespace std::string_literals;
constexpr uint8_t _v(const char c) {
const char a = c | 0x20;
return (a >= '0' && a <= '9') ? a - '0' :
(a >= 'a' && a <= 'f') ? 10 + a - 'a' :
throw std::logic_error { "invalid hex character in GUID"s };
}
constexpr uint8_t _d(const char * str, const int index) {
return ((_v(str[index]) << 4) + _v(str[index + 1]));
}
constexpr guid_t parse(const char * str) {
return {
_d(str, 0), _d(str, 2), _d(str, 4), _d(str, 6),
_d(str, 9), _d(str, 11),
_d(str, 14), _d(str, 16),
_d(str, 19), _d(str, 21),
_d(str, 24), _d(str, 26), _d(str, 28), _d(str, 30), _d(str, 32), _d(str, 34)
};
}
}
constexpr guid_t operator "" _guid(const char * str, size_t size) {
using namespace std::string_literals;
if (size != 36 && size != 38)
throw std::logic_error { "invalid GUID format, allowed format are {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} or XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"s };
return ct_guid::parse(size == 36 ? str : str + 1);
}
usage
guid_t g = "{6359f494-ec9d-495a-82e3-6d1a8623af79}"_guid;
Thanks for the inspiration. None of these worked with my memory layout for the GUID (byte order of uint64_t, uint64_t vs the split fields) but were helpful none the less in seeing what had to be done.
constexpr GuidData make_guid_helper(const char* begin)
{
GuidData result{};
for (size_t i = 0; i < 18; i++) {
if (i == 8 || i == 13)
continue;
result.upper_ = result.upper_ << 4 | parse_hex_digit(begin[i]);
}
for (size_t i = 19; i < 36; i++)
{
if (i == 23)
continue;
result.lower_ = result.lower_ << 4 | parse_hex_digit(begin[i]);
}
return result;
}
Now I just need to work out base62 ids.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Very nicely done. Thank you!