Skip to content

Instantly share code, notes, and snippets.

@AlexBAV
Last active August 29, 2023 23:38
  • Star 20 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save AlexBAV/b58e92d7632bae5d6f5947be455f796f to your computer and use it in GitHub Desktop.
Constexpr GUID parsing (parsing string GUIDs at compile-time)
//-------------------------------------------------------------------------------------------------------
// 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));
}
}
}
@tobias-loew
Copy link

tobias-loew commented Dec 21, 2018

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?

@LowLevelMahn
Copy link

LowLevelMahn commented Jul 24, 2020

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 :)

@muhamad
Copy link

muhamad commented Sep 14, 2022

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;

@JSandusky
Copy link

JSandusky commented May 3, 2023

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