@@ -104,14 +104,24 @@ struct FlagsHelper final {
104104
105105 // Range that is scanned by the compiler
106106 static constexpr size_t MinScan{0 };
107- static constexpr size_t MarginScan{1 }; // Scan one past to check for overpopulation
108- static constexpr size_t MaxUnderScan{std::numeric_limits<U >::digits}; // Maximum digits the underlying type has
107+ static constexpr size_t MarginScan{1 }; // Scan one past to check for overpopulation
108+ static constexpr size_t MaxUnderScan{std::numeric_limits<UMax >::digits}; // Maximum digits the underlying type has
109109 static constexpr size_t MaxScan{MaxUnderScan + MarginScan};
110110
111- // Checks if a given 'localation' contains an enum.
111+ // Checks if a given 'location' contains an enum.
112+ // Note: intentionally processes out-of-range enum values during compile-time
113+ // reflection to detect which enum members are valid
112114 template <E e>
113115 static constexpr bool isValid () noexcept
114116 {
117+ #if defined __clang__
118+ #pragma clang diagnostic push
119+ #pragma clang diagnostic ignored "-Wenum-constexpr-conversion"
120+ #elif defined __GNUC__
121+ #pragma GCC diagnostic push
122+ #pragma GCC diagnostic ignored "-Wenum-constexpr-conversion"
123+ #endif
124+
115125 constexpr auto tp{tpeek_v<e>.rfind (getSpec<SVal::Start, SType::Enum_t>())};
116126 if constexpr (tp == std::string_view::npos) {
117127 return false ;
@@ -128,13 +138,16 @@ struct FlagsHelper final {
128138 // check if this is an anonymous enum
129139 return true ;
130140 }
131- return false ;
132- #else
141+ #else // __GNUC__
133142 else if constexpr (tpeek_v<e>[tp + getSpec<SVal::Start, SType::Enum_t>().size ()] != ' (' && tpeek_v<e>.find_first_of (getSpec<SVal::End, SType::Enum_t>(), tp + getSpec<SVal::Start, SType::Enum_t>().size ()) != std::string_view::npos) {
134143 return true ;
135- } else {
136- return false ;
137144 }
145+ #endif
146+ return false ;
147+ #if defined __clang__
148+ #pragma clang diagnostic pop
149+ #elif defined __GNUC__
150+ #pragma GCC diagnostic pop
138151#endif
139152 }
140153
@@ -161,7 +174,7 @@ struct FlagsHelper final {
161174 static constexpr auto Max_v{Values.back ()}; // Enum last entry
162175 static constexpr auto Min_u_v{static_cast <size_t >(Min_v)}; // Enum first entry as size_t
163176 static constexpr auto Max_u_v{static_cast <size_t >(Max_v)}; // Enum last entry as size_t
164- static_assert (Max_u_v < std::numeric_limits<U>::digits, " Max Bit is beyond allow range defered from underlying type" );
177+ static_assert (Max_u_v < std::numeric_limits<U>::digits, " Max Bit is beyond allow range deferred from underlying type" );
165178 static constexpr bool isContinuous () noexcept { return (Max_u_v - Min_u_v + 1 ) == count (); } // Is the enum continuous
166179 static constexpr UMax makeMaxRep (size_t min, size_t max)
167180 {
@@ -258,7 +271,7 @@ struct FlagsHelper final {
258271 static constexpr std::optional<E> fromString (std::string_view str) noexcept
259272 {
260273 for (size_t i{0 }; i < count (); ++i) {
261- if (Names[i] == str || NamesScoped[i] == str) {
274+ if (isIEqual ( Names[i], str) || isIEqual ( NamesScoped[i], str) ) {
262275 return Values[i];
263276 }
264277 }
@@ -277,7 +290,7 @@ struct FlagsHelper final {
277290 return toLower (a) == toLower (b);
278291 }
279292
280- // Case-insensitive comparision for string_view.
293+ // Case-insensitive comparison for string_view.
281294 static constexpr bool isIEqual (std::string_view s1, std::string_view s2) noexcept
282295 {
283296 if (s1.size () != s2.size ()) {
@@ -294,7 +307,7 @@ struct FlagsHelper final {
294307 static constexpr std::string_view None{" none" };
295308 static constexpr bool hasNone () noexcept
296309 {
297- // check that enum does not contain memeber named 'none'
310+ // check that enum does not contain member named 'none'
298311 for (size_t i{0 }; i < count (); ++i) {
299312 if (isIEqual (Names[i], None)) {
300313 return true ;
@@ -306,7 +319,7 @@ struct FlagsHelper final {
306319 static constexpr std::string_view All{" all" };
307320 static constexpr bool hasAll () noexcept
308321 {
309- // check that enum does not contain memeber named 'all'
322+ // check that enum does not contain member named 'all'
310323 for (size_t i{0 }; i < count (); ++i) {
311324 if (isIEqual (Names[i], All)) {
312325 return true ;
@@ -332,7 +345,7 @@ concept EnumFlag = requires {
332345};
333346
334347/* *
335- * \brief Classs to aggregate and manage enum-based on-off flags.
348+ * \brief Class to aggregate and manage enum-based on-off flags.
336349 *
337350 * This class manages flags as bits in the underlying type of an enum (upto 64 bits), allowing
338351 * manipulation via enum member names. It supports operations akin to std::bitset
@@ -355,6 +368,7 @@ concept EnumFlag = requires {
355368template <EnumFlag E>
356369class EnumFlags
357370{
371+ static constexpr int DefaultBase{2 };
358372 using H = details::enum_flags::FlagsHelper<E>;
359373 using U = std::underlying_type_t <E>;
360374 U mBits {0 };
@@ -388,9 +402,9 @@ class EnumFlags
388402 std::for_each (flags.begin (), flags.end (), [this ](const E f) noexcept { mBits |= to_bit (f); });
389403 }
390404 // Init from a string.
391- EnumFlags (const std::string& str)
405+ EnumFlags (std::string_view str, int base = DefaultBase )
392406 {
393- set (str);
407+ set (str, base );
394408 }
395409 // Destructor.
396410 constexpr ~EnumFlags () = default ;
@@ -413,14 +427,14 @@ class EnumFlags
413427 // Sets flags from a string representation.
414428 // This can be either from a number representation (binary or digits) or
415429 // a concatenation of the enums members name e.g., 'Enum1|Enum2|...'
416- void set (const std::string& s = " " , int base = 2 )
430+ void set (std::string_view s , int base = DefaultBase )
417431 {
418- // on throw restore previous state and rethrow
419- const U prev = mBits ;
420- reset ();
421432 if (s.empty ()) { // no-op
422433 return ;
423434 }
435+ // on throw restore previous state and rethrow
436+ const U prev = mBits ;
437+ reset ();
424438 try {
425439 setImpl (s, base);
426440 } catch (const std::exception& e) {
@@ -441,39 +455,42 @@ class EnumFlags
441455 }
442456
443457 // Resets a specific flag.
444- template <typename T>
445- requires std::is_same_v<T, E>
458+ template <std::same_as<E> T>
446459 constexpr void reset (T t)
447460 {
448461 mBits &= ~to_bit (t);
449462 }
450463
451464 // Tests if a specific flag is set.
452- template <typename T>
453- requires std::is_same_v<T, E>
465+ template <std::same_as<E> T>
454466 [[nodiscard]] constexpr bool test (T t) const noexcept
455467 {
456468 return (mBits & to_bit (t)) != None;
457469 }
458470
459471 // Tests if all specified flags are set.
460- template <typename ... Ts>
472+ template <std::same_as<E> ... Ts>
461473 [[nodiscard]] constexpr bool test (Ts... flags) const noexcept
462474 {
463475 return ((test (flags) && ...));
464476 }
465477
466478 // Sets a specific flag.
467- template <typename T>
468- requires std::is_same_v<T, E>
479+ template <std::same_as<E> T>
469480 constexpr void set (T t) noexcept
470481 {
471482 mBits |= to_bit (t);
472483 }
473484
485+ // Sets multiple specific flags.
486+ template <std::same_as<E>... Ts>
487+ constexpr void set (Ts... flags) noexcept
488+ {
489+ (set (flags), ...);
490+ }
491+
474492 // Toggles a specific flag.
475- template <typename T>
476- requires std::is_same_v<T, E>
493+ template <std::same_as<E> T>
477494 constexpr void toggle (T t) noexcept
478495 {
479496 mBits ^= to_bit (t);
@@ -538,8 +555,7 @@ class EnumFlags
538555 }
539556
540557 // Check if given flag is set.
541- template <typename T>
542- requires std::is_same_v<T, E>
558+ template <std::same_as<E> T>
543559 [[nodiscard]] constexpr bool operator [](const T t) const noexcept
544560 {
545561 return test (t);
@@ -564,26 +580,23 @@ class EnumFlags
564580 constexpr EnumFlags& operator =(EnumFlags&& o) = default ;
565581
566582 // Performs a bitwise OR with a flag.
567- template <typename T>
568- requires std::is_same_v<T, E>
583+ template <std::same_as<E> T>
569584 constexpr EnumFlags& operator |=(T t) noexcept
570585 {
571586 mBits |= to_bit (t);
572587 return *this ;
573588 }
574589
575590 // Performs a bitwise AND with a flag.
576- template <typename T>
577- requires std::is_same_v<T, E>
591+ template <std::same_as<E> T>
578592 constexpr EnumFlags& operator &=(T t) noexcept
579593 {
580594 mBits &= to_bit (t);
581595 return *this ;
582596 }
583597
584598 // Returns a flag set with a bitwise AND.
585- template <typename T>
586- requires std::is_same_v<T, E>
599+ template <std::same_as<E> T>
587600 constexpr EnumFlags operator &(T t) const noexcept
588601 {
589602 return EnumFlags (mBits & to_bit (t));
@@ -683,34 +696,91 @@ class EnumFlags
683696
684697 private:
685698 // Set implementation, bits was zeroed before.
686- void setImpl (const std::string& s, int base = 2 )
699+ void setImpl (std::string_view s, int base = 2 )
687700 {
701+ // Helper to check if character is valid for given base
702+ auto isValidForBase = [](unsigned char c, int base) -> bool {
703+ if (base == 2 ) {
704+ return c == ' 0' || c == ' 1' ;
705+ }
706+ if (base == 10 ) {
707+ return std::isdigit (c);
708+ }
709+ if (base == 16 ) {
710+ return std::isdigit (c) || (c >= ' a' && c <= ' f' ) || (c >= ' A' && c <= ' F' );
711+ }
712+ return false ;
713+ };
714+
715+ // hex
716+ if (base == 16 ) {
717+ std::string_view hex_str = s;
718+ // Strip optional 0x or 0X prefix
719+ if (s.size () >= 2 && s[0 ] == ' 0' && (s[1 ] == ' x' || s[1 ] == ' X' )) {
720+ hex_str = s.substr (2 );
721+ }
722+ if (hex_str.empty ()) {
723+ throw std::invalid_argument (" Empty hexadecimal string." );
724+ }
725+ if (!std::all_of (hex_str.begin (), hex_str.end (), [&](unsigned char c) { return isValidForBase (c, 16 ); })) {
726+ throw std::invalid_argument (" Invalid hexadecimal string." );
727+ }
728+ typename H::UMax v = std::stoul (std::string (hex_str), nullptr , 16 );
729+ if (v > H::MaxRep) {
730+ throw std::out_of_range (" Value exceeds enum range." );
731+ }
732+ mBits = static_cast <U>(v);
733+ return ;
734+ }
735+
736+ // decimal and binary
688737 if (std::all_of (s.begin (), s.end (), [](unsigned char c) { return std::isdigit (c); })) {
689- if (base == 2 ) { // check of only 0 and 1 in string
690- if (!std::all_of (s.begin (), s.end (), [](char c) { return c == ' 0' || c == ' 1' ; })) {
738+ if (base == 2 ) {
739+ // Binary: check only 0 and 1
740+ if (!std::all_of (s.begin (), s.end (), [&](unsigned char c) { return isValidForBase (c, 2 ); })) {
691741 throw std::invalid_argument (" Invalid binary string." );
692742 }
693743 }
694- typename H::UMax v = std::stoul (s , nullptr , base);
744+ typename H::UMax v = std::stoul (std::string (s) , nullptr , base);
695745 if (v > H::MaxRep) {
696- throw std::out_of_range (" Values exceeds enum range." );
746+ throw std::out_of_range (" Value exceeds enum range." );
697747 }
698748 mBits = static_cast <U>(v);
699- } else if (std::all_of (s.begin (), s.end (), [](unsigned char c) { return std::isalnum (c) != 0 || c == ' |' || c == ' ' || c == ' :' || c == ' ,' || c == ' ;' ; })) {
749+ }
750+ // enum name strings
751+ else if (std::all_of (s.begin (), s.end (), [](unsigned char c) { return std::isalnum (c) != 0 || c == ' |' || c == ' ' || c == ' :' || c == ' ,' || c == ' ;' ; })) {
700752 std::string cs{s};
701753 std::transform (cs.begin (), cs.end (), cs.begin (), [](unsigned char c) { return std::tolower (c); });
754+
702755 if (cs == H::All) {
703756 mBits = All;
704757 } else if (cs == H::None) {
705758 mBits = None;
706759 } else {
707- // accept as delimiter ' ', '|', ';', ','
760+ // Detect delimiter and ensure only one type is used
708761 char token = ' ' ;
709- std::string::size_type pos = s.find_first_of (" ,|;" );
710- if (pos != std::string::npos) {
711- token = s[pos];
762+ size_t pipePos = s.find (' |' );
763+ size_t commaPos = s.find (' ,' );
764+ size_t semiPos = s.find (' ;' );
765+
766+ // Count how many different delimiters exist
767+ int delimiterCount = (pipePos != std::string_view::npos ? 1 : 0 ) +
768+ (commaPos != std::string_view::npos ? 1 : 0 ) +
769+ (semiPos != std::string_view::npos ? 1 : 0 );
770+
771+ if (delimiterCount > 1 ) {
772+ throw std::invalid_argument (" Mixed delimiters not allowed!" );
773+ }
774+
775+ if (pipePos != std::string_view::npos) {
776+ token = ' |' ;
777+ } else if (commaPos != std::string_view::npos) {
778+ token = ' ,' ;
779+ } else if (semiPos != std::string_view::npos) {
780+ token = ' ;' ;
712781 }
713- for (const auto & tok : Str::tokenize (s, token)) {
782+
783+ for (const auto & tok : Str::tokenize (std::string (s), token)) {
714784 if (auto e = H::fromString (tok)) {
715785 mBits |= to_bit (*e);
716786 } else {
0 commit comments