@@ -104,8 +104,8 @@ 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
111111 // Checks if a given 'localation' contains an enum.
@@ -116,7 +116,10 @@ struct FlagsHelper final {
116116 if constexpr (tp == std::string_view::npos) {
117117 return false ;
118118 }
119+ // intentionally casting out-of-range values during reflection to detect which enum members are valid
119120#if defined __clang__
121+ #pragma clang diagnostic push
122+ #pragma clang diagnostic ignored "-Wenum-constexpr-conversion"
120123 else if constexpr (tpeek_v<e>[tp + getSpec<SVal::Start, SType::Enum_t>().size ()] == ' (' ) {
121124 if constexpr (tpeek_v<e>[tp + getSpec<SVal::Start, SType::Enum_t>().size () + 1 ] == ' (' ) {
122125 return false ;
@@ -129,12 +132,18 @@ struct FlagsHelper final {
129132 return true ;
130133 }
131134 return false ;
132- #else
135+ #pragma clang diagnostic pop
136+ #elif defined __GNUC__
137+ #pragma GCC diagnostic push
138+ #pragma GCC diagnostic ignored "-Wenum-constexpr-conversion"
133139 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) {
134140 return true ;
135141 } else {
136142 return false ;
137143 }
144+ #pragma GCC diagnostic pop
145+ #else
146+ #error using unsupported compiler
138147#endif
139148 }
140149
@@ -161,7 +170,7 @@ struct FlagsHelper final {
161170 static constexpr auto Max_v{Values.back ()}; // Enum last entry
162171 static constexpr auto Min_u_v{static_cast <size_t >(Min_v)}; // Enum first entry as size_t
163172 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" );
173+ static_assert (Max_u_v < std::numeric_limits<U>::digits, " Max Bit is beyond allow range deferred from underlying type" );
165174 static constexpr bool isContinuous () noexcept { return (Max_u_v - Min_u_v + 1 ) == count (); } // Is the enum continuous
166175 static constexpr UMax makeMaxRep (size_t min, size_t max)
167176 {
@@ -258,7 +267,7 @@ struct FlagsHelper final {
258267 static constexpr std::optional<E> fromString (std::string_view str) noexcept
259268 {
260269 for (size_t i{0 }; i < count (); ++i) {
261- if (Names[i] == str || NamesScoped[i] == str) {
270+ if (isIEqual ( Names[i], str) || isIEqual ( NamesScoped[i], str) ) {
262271 return Values[i];
263272 }
264273 }
@@ -277,7 +286,7 @@ struct FlagsHelper final {
277286 return toLower (a) == toLower (b);
278287 }
279288
280- // Case-insensitive comparision for string_view.
289+ // Case-insensitive comparison for string_view.
281290 static constexpr bool isIEqual (std::string_view s1, std::string_view s2) noexcept
282291 {
283292 if (s1.size () != s2.size ()) {
@@ -294,7 +303,7 @@ struct FlagsHelper final {
294303 static constexpr std::string_view None{" none" };
295304 static constexpr bool hasNone () noexcept
296305 {
297- // check that enum does not contain memeber named 'none'
306+ // check that enum does not contain member named 'none'
298307 for (size_t i{0 }; i < count (); ++i) {
299308 if (isIEqual (Names[i], None)) {
300309 return true ;
@@ -306,7 +315,7 @@ struct FlagsHelper final {
306315 static constexpr std::string_view All{" all" };
307316 static constexpr bool hasAll () noexcept
308317 {
309- // check that enum does not contain memeber named 'all'
318+ // check that enum does not contain member named 'all'
310319 for (size_t i{0 }; i < count (); ++i) {
311320 if (isIEqual (Names[i], All)) {
312321 return true ;
@@ -332,7 +341,7 @@ concept EnumFlag = requires {
332341};
333342
334343/* *
335- * \brief Classs to aggregate and manage enum-based on-off flags.
344+ * \brief Class to aggregate and manage enum-based on-off flags.
336345 *
337346 * This class manages flags as bits in the underlying type of an enum (upto 64 bits), allowing
338347 * manipulation via enum member names. It supports operations akin to std::bitset
@@ -355,6 +364,7 @@ concept EnumFlag = requires {
355364template <EnumFlag E>
356365class EnumFlags
357366{
367+ static constexpr int DefaultBase{2 };
358368 using H = details::enum_flags::FlagsHelper<E>;
359369 using U = std::underlying_type_t <E>;
360370 U mBits {0 };
@@ -388,9 +398,9 @@ class EnumFlags
388398 std::for_each (flags.begin (), flags.end (), [this ](const E f) noexcept { mBits |= to_bit (f); });
389399 }
390400 // Init from a string.
391- EnumFlags (const std::string& str)
401+ EnumFlags (std::string_view str, int base = DefaultBase )
392402 {
393- set (str);
403+ set (str, base );
394404 }
395405 // Destructor.
396406 constexpr ~EnumFlags () = default ;
@@ -413,14 +423,14 @@ class EnumFlags
413423 // Sets flags from a string representation.
414424 // This can be either from a number representation (binary or digits) or
415425 // a concatenation of the enums members name e.g., 'Enum1|Enum2|...'
416- void set (const std::string& s = " " , int base = 2 )
426+ void set (std::string_view s , int base = DefaultBase )
417427 {
418- // on throw restore previous state and rethrow
419- const U prev = mBits ;
420- reset ();
421428 if (s.empty ()) { // no-op
422429 return ;
423430 }
431+ // on throw restore previous state and rethrow
432+ const U prev = mBits ;
433+ reset ();
424434 try {
425435 setImpl (s, base);
426436 } catch (const std::exception& e) {
@@ -441,39 +451,42 @@ class EnumFlags
441451 }
442452
443453 // Resets a specific flag.
444- template <typename T>
445- requires std::is_same_v<T, E>
454+ template <std::same_as<E> T>
446455 constexpr void reset (T t)
447456 {
448457 mBits &= ~to_bit (t);
449458 }
450459
451460 // Tests if a specific flag is set.
452- template <typename T>
453- requires std::is_same_v<T, E>
461+ template <std::same_as<E> T>
454462 [[nodiscard]] constexpr bool test (T t) const noexcept
455463 {
456464 return (mBits & to_bit (t)) != None;
457465 }
458466
459467 // Tests if all specified flags are set.
460- template <typename ... Ts>
468+ template <std::same_as<E> ... Ts>
461469 [[nodiscard]] constexpr bool test (Ts... flags) const noexcept
462470 {
463471 return ((test (flags) && ...));
464472 }
465473
466474 // Sets a specific flag.
467- template <typename T>
468- requires std::is_same_v<T, E>
475+ template <std::same_as<E> T>
469476 constexpr void set (T t) noexcept
470477 {
471478 mBits |= to_bit (t);
472479 }
473480
481+ // Sets multiple specific flags.
482+ template <std::same_as<E>... Ts>
483+ constexpr void set (Ts... flags) noexcept
484+ {
485+ (set (flags), ...);
486+ }
487+
474488 // Toggles a specific flag.
475- template <typename T>
476- requires std::is_same_v<T, E>
489+ template <std::same_as<E> T>
477490 constexpr void toggle (T t) noexcept
478491 {
479492 mBits ^= to_bit (t);
@@ -538,8 +551,7 @@ class EnumFlags
538551 }
539552
540553 // Check if given flag is set.
541- template <typename T>
542- requires std::is_same_v<T, E>
554+ template <std::same_as<E> T>
543555 [[nodiscard]] constexpr bool operator [](const T t) const noexcept
544556 {
545557 return test (t);
@@ -564,26 +576,23 @@ class EnumFlags
564576 constexpr EnumFlags& operator =(EnumFlags&& o) = default ;
565577
566578 // Performs a bitwise OR with a flag.
567- template <typename T>
568- requires std::is_same_v<T, E>
579+ template <std::same_as<E> T>
569580 constexpr EnumFlags& operator |=(T t) noexcept
570581 {
571582 mBits |= to_bit (t);
572583 return *this ;
573584 }
574585
575586 // Performs a bitwise AND with a flag.
576- template <typename T>
577- requires std::is_same_v<T, E>
587+ template <std::same_as<E> T>
578588 constexpr EnumFlags& operator &=(T t) noexcept
579589 {
580590 mBits &= to_bit (t);
581591 return *this ;
582592 }
583593
584594 // Returns a flag set with a bitwise AND.
585- template <typename T>
586- requires std::is_same_v<T, E>
595+ template <std::same_as<E> T>
587596 constexpr EnumFlags operator &(T t) const noexcept
588597 {
589598 return EnumFlags (mBits & to_bit (t));
@@ -683,34 +692,91 @@ class EnumFlags
683692
684693 private:
685694 // Set implementation, bits was zeroed before.
686- void setImpl (const std::string& s, int base = 2 )
695+ void setImpl (std::string_view s, int base = 2 )
687696 {
697+ // Helper to check if character is valid for given base
698+ auto isValidForBase = [](unsigned char c, int base) -> bool {
699+ if (base == 2 ) {
700+ return c == ' 0' || c == ' 1' ;
701+ }
702+ if (base == 10 ) {
703+ return std::isdigit (c);
704+ }
705+ if (base == 16 ) {
706+ return std::isdigit (c) || (c >= ' a' && c <= ' f' ) || (c >= ' A' && c <= ' F' );
707+ }
708+ return false ;
709+ };
710+
711+ // hex
712+ if (base == 16 ) {
713+ std::string_view hex_str = s;
714+ // Strip optional 0x or 0X prefix
715+ if (s.size () >= 2 && s[0 ] == ' 0' && (s[1 ] == ' x' || s[1 ] == ' X' )) {
716+ hex_str = s.substr (2 );
717+ }
718+ if (hex_str.empty ()) {
719+ throw std::invalid_argument (" Empty hexadecimal string." );
720+ }
721+ if (!std::all_of (hex_str.begin (), hex_str.end (), [&](unsigned char c) { return isValidForBase (c, 16 ); })) {
722+ throw std::invalid_argument (" Invalid hexadecimal string." );
723+ }
724+ typename H::UMax v = std::stoul (std::string (hex_str), nullptr , 16 );
725+ if (v > H::MaxRep) {
726+ throw std::out_of_range (" Value exceeds enum range." );
727+ }
728+ mBits = static_cast <U>(v);
729+ return ;
730+ }
731+
732+ // decimal and binary
688733 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' ; })) {
734+ if (base == 2 ) {
735+ // Binary: check only 0 and 1
736+ if (!std::all_of (s.begin (), s.end (), [&](unsigned char c) { return isValidForBase (c, 2 ); })) {
691737 throw std::invalid_argument (" Invalid binary string." );
692738 }
693739 }
694- typename H::UMax v = std::stoul (s , nullptr , base);
740+ typename H::UMax v = std::stoul (std::string (s) , nullptr , base);
695741 if (v > H::MaxRep) {
696- throw std::out_of_range (" Values exceeds enum range." );
742+ throw std::out_of_range (" Value exceeds enum range." );
697743 }
698744 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 == ' ;' ; })) {
745+ }
746+ // enum name strings
747+ else if (std::all_of (s.begin (), s.end (), [](unsigned char c) { return std::isalnum (c) != 0 || c == ' |' || c == ' ' || c == ' :' || c == ' ,' || c == ' ;' ; })) {
700748 std::string cs{s};
701749 std::transform (cs.begin (), cs.end (), cs.begin (), [](unsigned char c) { return std::tolower (c); });
750+
702751 if (cs == H::All) {
703752 mBits = All;
704753 } else if (cs == H::None) {
705754 mBits = None;
706755 } else {
707- // accept as delimiter ' ', '|', ';', ','
756+ // Detect delimiter and ensure only one type is used
708757 char token = ' ' ;
709- std::string::size_type pos = s.find_first_of (" ,|;" );
710- if (pos != std::string::npos) {
711- token = s[pos];
758+ size_t pipePos = s.find (' |' );
759+ size_t commaPos = s.find (' ,' );
760+ size_t semiPos = s.find (' ;' );
761+
762+ // Count how many different delimiters exist
763+ int delimiterCount = (pipePos != std::string_view::npos ? 1 : 0 ) +
764+ (commaPos != std::string_view::npos ? 1 : 0 ) +
765+ (semiPos != std::string_view::npos ? 1 : 0 );
766+
767+ if (delimiterCount > 1 ) {
768+ throw std::invalid_argument (" Mixed delimiters not allowed!" );
769+ }
770+
771+ if (pipePos != std::string_view::npos) {
772+ token = ' |' ;
773+ } else if (commaPos != std::string_view::npos) {
774+ token = ' ,' ;
775+ } else if (semiPos != std::string_view::npos) {
776+ token = ' ;' ;
712777 }
713- for (const auto & tok : Str::tokenize (s, token)) {
778+
779+ for (const auto & tok : Str::tokenize (std::string (s), token)) {
714780 if (auto e = H::fromString (tok)) {
715781 mBits |= to_bit (*e);
716782 } else {
0 commit comments