Created
December 11, 2024 03:21
-
-
Save fredemmott/ec4cf455421c59c08ddb2b930febab65 to your computer and use it in GitHub Desktop.
Compile time string-to-GUID
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
| // Copyright 2024 Fred Emmott <fred@fredemmott.com> | |
| // SPDX-License-Identifier: MIT | |
| #pragma once | |
| // Usage: | |
| // | |
| // constexpr GUID TestGuid = "9add179d-b650-4eca-b471-88ab38c2bd46"_guid; | |
| // constexpr GUID TestGuid2 = "{2f896df5-d701-42ea-ab02-cbd50af16516}"_guid; | |
| namespace detail::compile_time_guid { | |
| // Marker for more readable compile errors | |
| template <size_t ActualSize> | |
| struct guids_must_be_36_chars_or_38_with_braces { | |
| guids_must_be_36_chars_or_38_with_braces() = delete; | |
| }; | |
| template <char... Value> | |
| struct GuidParser : guids_must_be_36_chars_or_38_with_braces<sizeof...(Value)> { | |
| }; | |
| constexpr size_t SizeOfStringGuid | |
| = sizeof("00000000-0000-0000-0000-000000000000") - 1; | |
| enum class ByteIndexKind { | |
| LittleEndian, | |
| PerByte, | |
| }; | |
| template <char... Value> | |
| requires(sizeof...(Value) == SizeOfStringGuid) | |
| struct GuidParser<Value...> { | |
| static consteval GUID Parse() noexcept { | |
| // 00000000-0000-0000-0000-000000000000 | |
| // 8 13 18 23 36 <<< char offsets (separators) | |
| // 0 9 14 19 24 <<< char offset | |
| // 0 4 6 8 10 <<< byte offsets | |
| static_assert(ArrayValue.at(8) == '-'); | |
| static_assert(ArrayValue.at(13) == '-'); | |
| static_assert(ArrayValue.at(18) == '-'); | |
| static_assert(ArrayValue.at(23) == '-'); | |
| using enum ByteIndexKind; | |
| uint8_t ret[sizeof(GUID)]; | |
| ParseRange<0, 0, 8, LittleEndian>(ret); | |
| ParseRange<4, 9, 13, LittleEndian>(ret); | |
| ParseRange<6, 14, 18, LittleEndian>(ret); | |
| ParseRange<8, 19, 23, PerByte>(ret); | |
| ParseRange<10, 24, 36, PerByte>(ret); | |
| return std::bit_cast<GUID>(ret); | |
| } | |
| private: | |
| static constexpr auto ArrayValue = std::array {Value...}; | |
| template < | |
| size_t TByteOffset, | |
| size_t TCharBegin, | |
| size_t TCharEnd, | |
| ByteIndexKind TByteIndexKind> | |
| consteval static void ParseRange(uint8_t ret[38]) noexcept { | |
| static_assert( | |
| (TCharEnd - TCharBegin) % 2 == 0, "Hex digits should come in pairs"); | |
| []<size_t... I>(uint8_t ret[38], std::index_sequence<I...>) { | |
| constexpr auto byteIndex = [](size_t It) { | |
| if constexpr (TByteIndexKind == ByteIndexKind::LittleEndian) { | |
| constexpr auto TotalBytes = (TCharEnd - TCharBegin) / 2; | |
| return TotalBytes - (It + 1) + TByteOffset; | |
| } else { | |
| return TByteOffset + It; | |
| } | |
| }; | |
| ((ret[byteIndex(I)] = FromHexDigitPair<(2 * I) + TCharBegin>()), ...); | |
| }(ret, std::make_index_sequence<(TCharEnd - TCharBegin) / 2>()); | |
| } | |
| template <size_t I> | |
| static consteval uint8_t FromHexDigitPair() { | |
| return FromHexDigit<I>() << 4 | FromHexDigit<I + 1>(); | |
| } | |
| template <size_t I> | |
| static consteval uint8_t FromHexDigit() { | |
| const auto it = ArrayValue.at(I); | |
| if constexpr (it >= '0' && it <= '9') { | |
| return it - '0'; | |
| } else if constexpr (it >= 'a' && it <= 'f') { | |
| return it - 'a' + 10; | |
| } else if constexpr (it >= 'A' && it <= 'F') { | |
| return it - 'A' + 10; | |
| } else { | |
| static_assert(false, "Out-of-range character"); | |
| std::unreachable();// bogus 'not all control paths return a value' | |
| } | |
| } | |
| }; | |
| static constexpr size_t SizeOfStringGuidWithBraces | |
| = sizeof("{00000000-0000-0000-0000-000000000000}") - 1; | |
| template <char... Value> | |
| requires(sizeof...(Value) == SizeOfStringGuidWithBraces) | |
| struct GuidParser<Value...> { | |
| static consteval GUID Parse() noexcept { | |
| static_assert(ArrayValue.front() == '{'); | |
| static_assert(ArrayValue.back() == '}'); | |
| static_assert(SizeOfStringGuid == SizeOfStringGuidWithBraces - 2); | |
| return []<std::size_t... Index>(std::index_sequence<Index...>) { | |
| return GuidParser<std::get<Index + 1>(ArrayValue)...>::Parse(); | |
| }(std::make_index_sequence<SizeOfStringGuid> {}); | |
| } | |
| private: | |
| static constexpr auto ArrayValue = std::array {Value...}; | |
| }; | |
| }// namespace detail::compile_time_guid | |
| namespace detail { | |
| template <std::size_t N> | |
| struct CompileTimeStringHelper { | |
| char mValue[N - 1] {}; | |
| consteval CompileTimeStringHelper(const char (&value)[N]) { | |
| for (size_t i = 0; i < (N - 1); ++i) { | |
| mValue[i] = value[i]; | |
| } | |
| } | |
| }; | |
| }// namespace detail | |
| template <detail::CompileTimeStringHelper CTS> | |
| consteval GUID operator"" _guid() { | |
| return []<std::size_t... Index>(std::index_sequence<Index...>) { | |
| // Construct an instance so we get the nice compile error if it's the wrong | |
| // size | |
| return detail::compile_time_guid::GuidParser<CTS.mValue[Index]...> {} | |
| .Parse(); | |
| }(std::make_index_sequence<sizeof(CTS.mValue)> {}); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment