-
-
Save AlexBAV/b58e92d7632bae5d6f5947be455f796f to your computer and use it in GitHub Desktop.
//------------------------------------------------------------------------------------------------------- | |
// 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)); | |
} | |
} | |
} |
UDL Usage:
using namespace guid_parse::literals;
constexpr const GUID clsid = "{EFECF0A1-399E-40B8-A13C-ACE28DB40212}"_guid;
I'm concerned by the amount of hardcoded magic integers, and multiple 36 - 39. This looks bad. DRY.
Very nicely done. Thank you!
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.
Usage: