From 9da26791fe55f2642bc859b651a25d907201d695 Mon Sep 17 00:00:00 2001 From: Dmitriy Khaustov aka xDimon Date: Wed, 7 May 2025 19:08:52 +0300 Subject: [PATCH 1/6] feature: validate_onebyte_enum_over_refletion Signed-off-by: Dmitriy Khaustov aka xDimon --- include/scale/detail/enum.hpp | 261 +++++++++++++++++++++++----------- test/scale_enum_test.cpp | 81 ++++++++++- 2 files changed, 252 insertions(+), 90 deletions(-) diff --git a/include/scale/detail/enum.hpp b/include/scale/detail/enum.hpp index 3da5cf4..1d02f95 100644 --- a/include/scale/detail/enum.hpp +++ b/include/scale/detail/enum.hpp @@ -5,114 +5,200 @@ */ /** - * @brief Implements encoding and validation of enumeration values using SCALE - * encoding. + * @brief Implements encoding and validation of enumeration values using SCALE. * * This file provides utilities for handling enumerations in SCALE encoding. * It allows defining constraints on enum values via `enum_traits` * specializations, ensuring that only valid values are encoded or decoded. - * There are two ways to specialize an enumeration type: * - * 1. **Defining a range of valid values** using `min_value` and `max_value`. - * 2. **Providing an explicit list of valid values** using `valid_values`. + * There are two ways to specialize an enumeration type: + * - Define a range using `min_value` and `max_value`. + * - Provide an explicit list using `valid_values`. * - * The validation ensures that any decoded value belongs to the expected set, - * reducing the risk of unexpected errors when processing SCALE-encoded data. + * Validation guarantees decoded values are within expected bounds, + * reducing risk when handling SCALE-encoded data. */ #pragma once +#include +#include +#include #include #include #include #include -#include namespace scale { namespace detail::enumerations { +#if defined(__clang__) || defined(__GNUC__) +#define ENUM_NAME_PRETTY_FUNCTION __PRETTY_FUNCTION__ + constexpr std::string_view enum_prefix = + "constexpr std::string_view enum_name_impl() [with auto V = "; + constexpr std::string_view enum_suffix = "]"; +#else +#error Unsupported compiler +#endif + + /** + * @brief Extracts enumeration name from the compiler-specific string. + * @tparam V The enum value. + */ + template + constexpr std::string_view enum_name_impl() { + constexpr std::string_view func = ENUM_NAME_PRETTY_FUNCTION; + constexpr std::size_t start = func.find(enum_prefix) + enum_prefix.size(); + constexpr std::size_t end = func.find(enum_suffix, start); + constexpr std::string_view full = func.substr(start, end - start); + constexpr std::size_t colons = full.find("::"); + if (colons == std::string_view::npos) return {}; + return full.substr(colons + 2); + } + + /** + * @brief Concept that checks if a type is an enumeration. + */ template concept Enumeration = std::is_enum_v>; /** - * @brief Traits for enum validation. - * - * Provides two specialization choices: - * - `min_value` and `max_value` convertible to `std::underlying_type_t`. - * - A container of `std::underlying_type_t` named `valid_values`, - * listing valid values. - * - * @note Check the macros below for specialization convenience. - * @tparam E The enum type. + * @brief Traits struct to be specialized for enumeration validation. */ template - requires std::is_enum_v - struct [[deprecated( - "Check the doc comment to see the specialization options")]] // - enum_traits final { - /// Used to detect an unspecialized enum_traits - static constexpr bool is_default = true; - }; + struct enum_traits; // default not specialized + + namespace detail_impl { + + template + concept HasValidValues = requires { enum_traits::valid_values; }; + + template + concept HasMinMax = requires { + enum_traits::min_value; + enum_traits::max_value; + }; + + template , U... Vs> + constexpr bool is_valid_enum_value_by_reflection_impl( + U value, std::integer_sequence) { + return ((enum_name_impl(Vs)>().size() > 0 + && static_cast(static_cast(Vs)) == value) + || ...); + } + + template + constexpr bool call_with_casted_signed_range( + std::underlying_type_t value, std::integer_sequence) { + using U = std::underlying_type_t; + constexpr int min = -128; + return is_valid_enum_value_by_reflection_impl( + value, std::integer_sequence(min + Is)...>{}); + } + + template + constexpr bool call_with_casted_unsigned_range( + std::underlying_type_t value, std::integer_sequence) { + using U = std::underlying_type_t; + return is_valid_enum_value_by_reflection_impl( + value, std::integer_sequence(Is)...>{}); + } + + /** + * @brief Fallback validation using reflection for enums of size 1 byte. + */ + template + requires(sizeof(std::underlying_type_t) == 1) + constexpr bool is_valid_enum_value_by_reflection( + std::underlying_type_t value) { + using U = std::underlying_type_t; + + if constexpr (std::is_signed_v) { + constexpr int min = -128; + constexpr int max = 127; + return call_with_casted_signed_range( + value, std::make_integer_sequence{}); + } else { + constexpr int max = 255; + return call_with_casted_unsigned_range( + value, std::make_integer_sequence{}); + } + } + + /** + * @brief Validates enum value using min/max range. + */ + template + requires HasMinMax> + constexpr bool is_valid_enum_value_range( + std::underlying_type_t> value) noexcept { + using E = std::decay_t; + constexpr auto Min = enum_traits::min_value; + constexpr auto Max = enum_traits::max_value; + return value >= Min && value <= Max; + } + + /** + * @brief Validates enum value against list of valid values. + */ + template + requires HasValidValues> + constexpr bool is_valid_enum_value_list( + std::underlying_type_t> value) noexcept { + using E = std::decay_t; + const auto &valid_values = enum_traits::valid_values; + return std::find(valid_values.begin(), + valid_values.end(), + static_cast(value)) + != valid_values.end(); + } + + } // namespace detail_impl /** - * @brief Checks if a given value is within a defined range of valid enum - * values. - * @tparam T The input type (expected to be an enum or convertible - * underlying type). - * @param value The value to check. - * @return True if the value is within the range, false otherwise. + * @brief Marker struct used for deprecated fallback validation. */ - template , - typename E_traits = enum_traits, - std::underlying_type_t Min = E_traits::min_value, - std::underlying_type_t Max = E_traits::max_value> - constexpr bool is_valid_enum_value( - std::underlying_type_t value) noexcept { - return value >= Min && value <= Max; - } + template + struct scale_enum_validation_warning; /** - * @brief Checks if a given value is within an explicitly defined set of - * valid enum values. - * @tparam T The input type (expected to be an enum or convertible - * underlying type). - * @param value The value to check. - * @return True if the value is listed in `valid_values`, false otherwise. + * @brief Fallback validator for unsupported enums. */ - template , - typename E_traits = enum_traits, - typename = decltype(E_traits::valid_values)> - constexpr bool is_valid_enum_value( - std::underlying_type_t value) noexcept { - const auto &valid_values = E_traits::valid_values; - return std::find(std::begin(valid_values), - std::end(valid_values), - static_cast(value)) - != std::end(valid_values); - } + template + struct [[deprecated( + "Cannot validate enum because no valid_values or min/max in " + "enum_traits and reflection is unavailable for enums with underlying " + "types >1 byte. " + "Define enum_traits to enable safe validation during SCALE " + "decoding.")]] scale_enum_validation_warning {}; /** - * @brief Default case for unspecialized enum types. - * - * This function always returns `true`, but a `static_assert` ensures that - * an explicit specialization of `enum_traits` is required. - * - * @tparam T The input type (expected to be an enum). - * @return Always true, but triggers a compilation error if used. + * @brief Central enum validation entry point. + * @tparam T Enum type. + * @param value The underlying integer value. + * @return true if value is valid. */ template - requires enum_traits>::is_default - [[deprecated( - "Please specialize scale::enum_traits for your enum so it can be " - "validated during decoding")]] + requires std::is_enum_v> constexpr bool is_valid_enum_value( - std::underlying_type_t>) noexcept { - return true; + std::underlying_type_t> value) noexcept { + using E = std::decay_t; + + if constexpr (detail_impl::HasValidValues) { + return detail_impl::is_valid_enum_value_list(value); + } else if constexpr (detail_impl::HasMinMax) { + return detail_impl::is_valid_enum_value_range(value); + } else if constexpr (sizeof(std::underlying_type_t) == 1) { + return detail_impl::is_valid_enum_value_by_reflection(value); + } else { + [[maybe_unused]] constexpr auto _ = + sizeof(scale_enum_validation_warning); + return true; + } } + } // namespace detail::enumerations using detail::enumerations::enum_traits; @@ -120,9 +206,10 @@ namespace scale { using detail::enumerations::is_valid_enum_value; /** - * @brief Encodes an enumeration into its underlying type. - * @param enumeration The enumeration value to encode. - * @param encoder SCALE encoder. + * @brief Encodes an enum value using SCALE encoding. + * @tparam T Enum type. + * @param enumeration Enum value to encode. + * @param encoder Encoder instance. */ void encode(const Enumeration auto &enumeration, Encoder &encoder) requires NoTagged @@ -133,9 +220,10 @@ namespace scale { } /** - * @brief Decodes an enumeration from its underlying type. - * @param v The enumeration value to decode into. - * @param decoder SCALE decoder. + * @brief Decodes an enum value using SCALE decoding. + * @tparam T Enum type. + * @param v Enum variable to store the decoded value. + * @param decoder Decoder instance. */ void decode(Enumeration auto &v, Decoder &decoder) requires NoTagged @@ -153,12 +241,12 @@ namespace scale { } // namespace scale /** - * @brief Defines a valid range for an enumeration. - * @note You should use this macro only in the global namespace - * @param enum_namespace The namespace of the enum. - * @param enum_name The enum type. - * @param min The minimum valid value. - * @param max The maximum valid value. + * @def SCALE_DEFINE_ENUM_VALUE_RANGE + * @brief Defines a valid value range for an enum. + * @param enum_namespace Namespace where enum is defined. + * @param enum_name Enum type name. + * @param min Minimum valid value. + * @param max Maximum valid value. */ #define SCALE_DEFINE_ENUM_VALUE_RANGE(enum_namespace, enum_name, min, max) \ template <> \ @@ -169,10 +257,11 @@ namespace scale { }; /** - * @brief Defines a valid list of values for an enumeration. - * @param enum_namespace The namespace of the enum. - * @param enum_name The enum type. - * @param ... The valid values. + * @def SCALE_DEFINE_ENUM_VALUE_LIST + * @brief Defines an explicit list of valid enum values. + * @param enum_namespace Namespace where enum is defined. + * @param enum_name Enum type name. + * @param ... List of valid values. */ #define SCALE_DEFINE_ENUM_VALUE_LIST(enum_namespace, enum_name, ...) \ template <> \ diff --git a/test/scale_enum_test.cpp b/test/scale_enum_test.cpp index bb8b442..38fbf05 100644 --- a/test/scale_enum_test.cpp +++ b/test/scale_enum_test.cpp @@ -4,6 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ +/** + * @brief Unit tests for SCALE encoding/decoding and validation of enums. + */ + #include #include #include @@ -12,11 +16,15 @@ using scale::DecodeError; using scale::impl::memory::decode; using scale::impl::memory::encode; +/** + * @brief Typed test fixture for enums with valid values. + * @tparam T Enum type. + */ template class ValidEnum : public testing::Test { protected: - const static std::string enum_name; - const static std::vector values; + const static std::string enum_name; ///< Name of the enum (for logging) + const static std::vector values; ///< List of valid values to test }; enum class Foo : uint16_t { A = 0, B = 1, C = 2 }; @@ -41,6 +49,13 @@ const std::vector ValidEnum::values{Bar::A, Bar::B, Bar::C}; SCALE_DEFINE_ENUM_VALUE_LIST(, Bar, Bar::A, Bar::B, Bar::C); +/** + * @brief Verifies that SCALE decoding of a valid enum restores the original + * value + * @given A valid enumeration value + * @when It is encoded and then decoded using SCALE + * @then The decoded value must be equal to the original one + */ TYPED_TEST(ValidEnum, ConsistentEncodingDecoding) { SCOPED_TRACE(TestFixture::enum_name); for (auto const ¶m : TestFixture::values) { @@ -50,19 +65,29 @@ TYPED_TEST(ValidEnum, ConsistentEncodingDecoding) { } } +/** + * @brief Ensures SCALE encoding yields raw underlying value + * @given A valid enumeration value + * @when It is encoded using SCALE + * @then The resulting bytes must match the encoding of its underlying value + */ TYPED_TEST(ValidEnum, CorrectEncoding) { using Type = std::underlying_type_t; for (auto const ¶m : TestFixture::values) { ASSERT_OUTCOME_SUCCESS(encoded, encode(param)); ASSERT_OUTCOME_SUCCESS(decoded, decode(encoded)); - EXPECT_EQ(decoded, static_cast>(param)); + EXPECT_EQ(decoded, static_cast(param)); } } +/** + * @brief Typed test fixture for enums with invalid values. + * @tparam T Enum type. + */ template class InvalidEnum : public testing::Test { protected: - const static std::string enum_name; + const static std::string enum_name; ///< Name of the enum (for logging) const static std::vector> invalid_values; }; @@ -79,6 +104,12 @@ const std::vector InvalidEnum::invalid_values{1, 2, 3}; using MyTypes = testing::Types; TYPED_TEST_SUITE(InvalidEnum, MyTypes); +/** + * @brief Ensures decoding fails for invalid enum values + * @given An invalid underlying value for an enumeration + * @when It is decoded as that enumeration type + * @then Decoding must fail with INVALID_ENUM_VALUE error + */ TYPED_TEST(InvalidEnum, ThrowsOnInvalidValue) { for (auto const ¶m : TestFixture::invalid_values) { ASSERT_OUTCOME_SUCCESS(encoded, encode(param)); @@ -86,3 +117,45 @@ TYPED_TEST(InvalidEnum, ThrowsOnInvalidValue) { DecodeError::INVALID_ENUM_VALUE); } } + +/** + * @brief Exhaustively validates values for a given enum type and underlying + * type. + * @tparam EnumT The enum type. + * @tparam UnderlyingT The corresponding integral type. + * @param valid List of explicitly valid enum values. + */ +template +void validate_enum_range(std::initializer_list valid) { + for (int i = std::numeric_limits::min(); + i <= std::numeric_limits::max(); + ++i) { + auto raw = static_cast(i); + auto val = static_cast(raw); + bool expected = std::find(valid.begin(), valid.end(), val) != valid.end(); + ASSERT_EQ(scale::is_valid_enum_value(raw), expected) + << "Failed at raw = " << +raw; + } +} + +/** + * @brief Tests validation by reflection for signed 1-byte enums + * @given An int8_t-based enumeration with known valid values + * @when Each possible raw value is validated using reflection + * @then Only the predefined enum values must be accepted as valid + */ +TEST(Enum, ValidatingByReflection_I8) { + enum class Baz : int8_t { A = -10, B = 0, C = 20 }; + validate_enum_range({Baz::A, Baz::B, Baz::C}); +} + +/** + * @brief Tests validation by reflection for unsigned 1-byte enums + * @given A uint8_t-based enumeration with known valid values + * @when Each possible raw value is validated using reflection + * @then Only the predefined enum values must be accepted as valid + */ +TEST(Enum, ValidatingByReflection_U8) { + enum class Qux : uint8_t { A = 0, B = 10, C = 20 }; + validate_enum_range({Qux::A, Qux::B, Qux::C}); +} From e0c47e2e50265fbf3adfd8fc3cb377710a252cbf Mon Sep 17 00:00:00 2001 From: Dmitriy Khaustov aka xDimon Date: Fri, 9 May 2025 02:24:57 +0300 Subject: [PATCH 2/6] fix: for g++-14 Signed-off-by: Dmitriy Khaustov aka xDimon --- include/scale/detail/enum.hpp | 44 ++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/include/scale/detail/enum.hpp b/include/scale/detail/enum.hpp index 1d02f95..dbb8516 100644 --- a/include/scale/detail/enum.hpp +++ b/include/scale/detail/enum.hpp @@ -34,11 +34,25 @@ namespace scale { namespace detail::enumerations { -#if defined(__clang__) || defined(__GNUC__) #define ENUM_NAME_PRETTY_FUNCTION __PRETTY_FUNCTION__ - constexpr std::string_view enum_prefix = - "constexpr std::string_view enum_name_impl() [with auto V = "; +#if defined(__clang__) + // clang-format off + // Invalid: + // "std::string_view scale::detail::enumerations::enum_name_impl() [V = (Baz)-128]" + // Valid: + // "std::string_view scale::detail::enumerations::enum_name_impl() [V = Enum_ValidatingByReflection_I8_Test::TestBody()::Baz::A]" + // clang-format on + constexpr std::string_view enum_prefix = "EnumValue = "; constexpr std::string_view enum_suffix = "]"; +#elif defined(__GNUC__) + // clang-format off + // Invalid: + // "std::string_view scale::detail::enumerations::enum_name_impl() [with auto V = (Enum_ValidatingByReflection_I8_Test::TestBody::Baz)-128; std::string_view = std::basic_string_view]" + // Valid: + // "std::string_view scale::detail::enumerations::enum_name_impl() [with auto V = Enum_ValidatingByReflection_I8_Test::TestBody::Baz::A; std::string_view = std::basic_string_view]" + // clang-format on + constexpr std::string_view enum_prefix = "EnumValue = "; + constexpr std::string_view enum_suffix = "; "; #else #error Unsupported compiler #endif @@ -47,15 +61,19 @@ namespace scale { * @brief Extracts enumeration name from the compiler-specific string. * @tparam V The enum value. */ - template - constexpr std::string_view enum_name_impl() { + template + std::string_view enum_name_impl() { constexpr std::string_view func = ENUM_NAME_PRETTY_FUNCTION; - constexpr std::size_t start = func.find(enum_prefix) + enum_prefix.size(); + constexpr std::size_t prefix_pos = func.find(enum_prefix); + assert(prefix_pos != std::string_view::npos); + constexpr std::size_t start = prefix_pos + enum_prefix.size(); + if (func[start] == '(') return {}; // Invalid, because wrapped by parenthesis) constexpr std::size_t end = func.find(enum_suffix, start); constexpr std::string_view full = func.substr(start, end - start); - constexpr std::size_t colons = full.find("::"); - if (colons == std::string_view::npos) return {}; - return full.substr(colons + 2); + constexpr std::size_t colons = full.rfind("::"); + if (colons == std::string_view::npos) return {}; // Invalid, no namespace + constexpr std::string_view result = full.substr(colons + 2); + return result; } /** @@ -163,6 +181,9 @@ namespace scale { template struct scale_enum_validation_warning; + template + struct always_false : std::false_type {}; + /** * @brief Fallback validator for unsupported enums. */ @@ -193,8 +214,9 @@ namespace scale { } else if constexpr (sizeof(std::underlying_type_t) == 1) { return detail_impl::is_valid_enum_value_by_reflection(value); } else { - [[maybe_unused]] constexpr auto _ = - sizeof(scale_enum_validation_warning); + static_assert(always_false::value, + "Cannot validate enum: define enum_traits<> with " + "min/max or valid_values."); return true; } } From fa03c2348e4b4e7094da9f7c6a99e4867413d75e Mon Sep 17 00:00:00 2001 From: Dmitriy Khaustov aka xDimon Date: Fri, 9 May 2025 02:37:07 +0300 Subject: [PATCH 3/6] clean: unused code Signed-off-by: Dmitriy Khaustov aka xDimon --- include/scale/detail/enum.hpp | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/include/scale/detail/enum.hpp b/include/scale/detail/enum.hpp index dbb8516..9c930f4 100644 --- a/include/scale/detail/enum.hpp +++ b/include/scale/detail/enum.hpp @@ -175,25 +175,8 @@ namespace scale { } // namespace detail_impl - /** - * @brief Marker struct used for deprecated fallback validation. - */ - template - struct scale_enum_validation_warning; - - template - struct always_false : std::false_type {}; - - /** - * @brief Fallback validator for unsupported enums. - */ template - struct [[deprecated( - "Cannot validate enum because no valid_values or min/max in " - "enum_traits and reflection is unavailable for enums with underlying " - "types >1 byte. " - "Define enum_traits to enable safe validation during SCALE " - "decoding.")]] scale_enum_validation_warning {}; + constexpr bool CannotValidateEnum = false; /** * @brief Central enum validation entry point. @@ -214,9 +197,9 @@ namespace scale { } else if constexpr (sizeof(std::underlying_type_t) == 1) { return detail_impl::is_valid_enum_value_by_reflection(value); } else { - static_assert(always_false::value, - "Cannot validate enum: define enum_traits<> with " - "min/max or valid_values."); + static_assert(CannotValidateEnum, + "Cannot validate enum: " + "define enum_traits<> with min/max or valid_values."); return true; } } From a1a389419ebf54f2214ad4e16df70793841a0300 Mon Sep 17 00:00:00 2001 From: Dmitriy Khaustov aka xDimon Date: Tue, 13 May 2025 09:29:56 +0300 Subject: [PATCH 4/6] fix: constexpr Signed-off-by: Dmitriy Khaustov aka xDimon --- include/scale/detail/enum.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/scale/detail/enum.hpp b/include/scale/detail/enum.hpp index 9c930f4..2029b2e 100644 --- a/include/scale/detail/enum.hpp +++ b/include/scale/detail/enum.hpp @@ -62,7 +62,7 @@ namespace scale { * @tparam V The enum value. */ template - std::string_view enum_name_impl() { + constexpr std::string_view enum_name_impl() { constexpr std::string_view func = ENUM_NAME_PRETTY_FUNCTION; constexpr std::size_t prefix_pos = func.find(enum_prefix); assert(prefix_pos != std::string_view::npos); @@ -72,8 +72,7 @@ namespace scale { constexpr std::string_view full = func.substr(start, end - start); constexpr std::size_t colons = full.rfind("::"); if (colons == std::string_view::npos) return {}; // Invalid, no namespace - constexpr std::string_view result = full.substr(colons + 2); - return result; + return full.substr(colons + 2); } /** From ff7670b299cb1dafd4004e90410db6130033d8aa Mon Sep 17 00:00:00 2001 From: Dmitriy Khaustov aka xDimon Date: Tue, 13 May 2025 09:31:15 +0300 Subject: [PATCH 5/6] hotfix Signed-off-by: Dmitriy Khaustov aka xDimon --- include/scale/detail/enum.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/scale/detail/enum.hpp b/include/scale/detail/enum.hpp index 2029b2e..8be75cc 100644 --- a/include/scale/detail/enum.hpp +++ b/include/scale/detail/enum.hpp @@ -65,7 +65,7 @@ namespace scale { constexpr std::string_view enum_name_impl() { constexpr std::string_view func = ENUM_NAME_PRETTY_FUNCTION; constexpr std::size_t prefix_pos = func.find(enum_prefix); - assert(prefix_pos != std::string_view::npos); + static_assert(prefix_pos != std::string_view::npos); constexpr std::size_t start = prefix_pos + enum_prefix.size(); if (func[start] == '(') return {}; // Invalid, because wrapped by parenthesis) constexpr std::size_t end = func.find(enum_suffix, start); From 2b2071d6a5167e7450469ad38139c6208c926015 Mon Sep 17 00:00:00 2001 From: Dmitriy Khaustov aka xDimon Date: Tue, 13 May 2025 19:44:01 +0300 Subject: [PATCH 6/6] fix: review issues Signed-off-by: Dmitriy Khaustov aka xDimon --- include/scale/detail/enum.hpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/include/scale/detail/enum.hpp b/include/scale/detail/enum.hpp index 8be75cc..6a2cf7b 100644 --- a/include/scale/detail/enum.hpp +++ b/include/scale/detail/enum.hpp @@ -62,16 +62,20 @@ namespace scale { * @tparam V The enum value. */ template - constexpr std::string_view enum_name_impl() { + consteval std::string_view enum_name_impl() { constexpr std::string_view func = ENUM_NAME_PRETTY_FUNCTION; constexpr std::size_t prefix_pos = func.find(enum_prefix); static_assert(prefix_pos != std::string_view::npos); constexpr std::size_t start = prefix_pos + enum_prefix.size(); - if (func[start] == '(') return {}; // Invalid, because wrapped by parenthesis) + if (func[start] == '(') + return {}; // Invalid: __PRETTY_FUNCTION__ prints invalid value of enum + // as number C-style-casted into enum type, i.e. `(Enum)-1`. constexpr std::size_t end = func.find(enum_suffix, start); constexpr std::string_view full = func.substr(start, end - start); constexpr std::size_t colons = full.rfind("::"); - if (colons == std::string_view::npos) return {}; // Invalid, no namespace + if (colons == std::string_view::npos) + return {}; // Invalid: __PRETTY_FUNCTION__ always prints valid value + // with type, i.e. `Enum::value` return full.substr(colons + 2); } @@ -90,12 +94,18 @@ namespace scale { namespace detail_impl { template - concept HasValidValues = requires { enum_traits::valid_values; }; + concept HasValidValues = requires { + { enum_traits::valid_values } -> std::ranges::range; + }; template concept HasMinMax = requires { - enum_traits::min_value; - enum_traits::max_value; + { + enum_traits::min_value + } -> std::convertible_to>; + { + enum_traits::max_value + } -> std::convertible_to>; }; template , U... Vs> @@ -183,8 +193,7 @@ namespace scale { * @param value The underlying integer value. * @return true if value is valid. */ - template - requires std::is_enum_v> + template constexpr bool is_valid_enum_value( std::underlying_type_t> value) noexcept { using E = std::decay_t;