@@ -54,10 +54,12 @@ concept EnumFlagHelper = requires {
5454// functions and also check via concepts expected properties of the enum.
5555// This is very much inspired by much more extensive libraries like magic_enum.
5656// Inspiration by its c++20 version (https://github.com/fix8mt/conjure_enum).
57+ // NOTE: Cannot detect if bit values past the underlying type are defined.
5758template <EnumFlagHelper E>
5859struct FlagsHelper final {
5960 using U = std::underlying_type_t <E>;
6061 using UMax = uint64_t ; // max represetable type
62+ static_assert (std::numeric_limits<U>::digits <= std::numeric_limits<UMax>::digits, " Underlying type has more digits than max supported digits" );
6163
6264 static constexpr bool isScoped () noexcept
6365 {
@@ -108,7 +110,7 @@ struct FlagsHelper final {
108110 static constexpr size_t MaxUnderScan{std::numeric_limits<U>::digits}; // Maximum digits the underlying type has
109111 static constexpr size_t MaxScan{MaxUnderScan + MarginScan};
110112
111- // Checks if a given 'localation ' contains an enum.
113+ // Checks if a given 'location ' contains an enum.
112114 template <E e>
113115 static constexpr bool isValid () noexcept
114116 {
@@ -128,14 +130,14 @@ struct FlagsHelper final {
128130 // check if this is an anonymous enum
129131 return true ;
130132 }
131- return false ;
132- #else
133+ #elif __GNUC__
133134 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) {
134135 return true ;
135- } else {
136- return false ;
137136 }
137+ #else
138+ #error Unsupported compiler
138139#endif
140+ return false ;
139141 }
140142
141143 // Extract which values are present in the enum by checking all values in
@@ -161,7 +163,7 @@ struct FlagsHelper final {
161163 static constexpr auto Max_v{Values.back ()}; // Enum last entry
162164 static constexpr auto Min_u_v{static_cast <size_t >(Min_v)}; // Enum first entry as size_t
163165 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" );
166+ static_assert (Max_u_v < std::numeric_limits<U>::digits, " Max Bit is beyond allow range deferred from underlying type" );
165167 static constexpr bool isContinuous () noexcept { return (Max_u_v - Min_u_v + 1 ) == count (); } // Is the enum continuous
166168 static constexpr UMax makeMaxRep (size_t min, size_t max)
167169 {
@@ -258,7 +260,7 @@ struct FlagsHelper final {
258260 static constexpr std::optional<E> fromString (std::string_view str) noexcept
259261 {
260262 for (size_t i{0 }; i < count (); ++i) {
261- if (Names[i] == str || NamesScoped[i] == str) {
263+ if (isIEqual ( Names[i], str) || isIEqual ( NamesScoped[i], str) ) {
262264 return Values[i];
263265 }
264266 }
@@ -277,7 +279,7 @@ struct FlagsHelper final {
277279 return toLower (a) == toLower (b);
278280 }
279281
280- // Case-insensitive comparision for string_view.
282+ // Case-insensitive comparison for string_view.
281283 static constexpr bool isIEqual (std::string_view s1, std::string_view s2) noexcept
282284 {
283285 if (s1.size () != s2.size ()) {
@@ -294,7 +296,7 @@ struct FlagsHelper final {
294296 static constexpr std::string_view None{" none" };
295297 static constexpr bool hasNone () noexcept
296298 {
297- // check that enum does not contain memeber named 'none'
299+ // check that enum does not contain member named 'none'
298300 for (size_t i{0 }; i < count (); ++i) {
299301 if (isIEqual (Names[i], None)) {
300302 return true ;
@@ -306,7 +308,7 @@ struct FlagsHelper final {
306308 static constexpr std::string_view All{" all" };
307309 static constexpr bool hasAll () noexcept
308310 {
309- // check that enum does not contain memeber named 'all'
311+ // check that enum does not contain member named 'all'
310312 for (size_t i{0 }; i < count (); ++i) {
311313 if (isIEqual (Names[i], All)) {
312314 return true ;
@@ -332,7 +334,7 @@ concept EnumFlag = requires {
332334};
333335
334336/* *
335- * \brief Classs to aggregate and manage enum-based on-off flags.
337+ * \brief Class to aggregate and manage enum-based on-off flags.
336338 *
337339 * This class manages flags as bits in the underlying type of an enum (upto 64 bits), allowing
338340 * manipulation via enum member names. It supports operations akin to std::bitset
@@ -355,6 +357,7 @@ concept EnumFlag = requires {
355357template <EnumFlag E>
356358class EnumFlags
357359{
360+ static constexpr int DefaultBase{2 };
358361 using H = details::enum_flags::FlagsHelper<E>;
359362 using U = std::underlying_type_t <E>;
360363 U mBits {0 };
@@ -388,9 +391,10 @@ class EnumFlags
388391 std::for_each (flags.begin (), flags.end (), [this ](const E f) noexcept { mBits |= to_bit (f); });
389392 }
390393 // Init from a string.
391- EnumFlags (const std::string& str)
394+ //
395+ explicit EnumFlags (const std::string& str, int base = DefaultBase)
392396 {
393- set (str);
397+ set (str, base );
394398 }
395399 // Destructor.
396400 constexpr ~EnumFlags () = default ;
@@ -413,14 +417,14 @@ class EnumFlags
413417 // Sets flags from a string representation.
414418 // This can be either from a number representation (binary or digits) or
415419 // a concatenation of the enums members name e.g., 'Enum1|Enum2|...'
416- void set (const std::string& s = " " , int base = 2 )
420+ void set (const std::string& s, int base = DefaultBase )
417421 {
418- // on throw restore previous state and rethrow
419- const U prev = mBits ;
420- reset ();
421422 if (s.empty ()) { // no-op
422423 return ;
423424 }
425+ // on throw restore previous state and rethrow
426+ const U prev = mBits ;
427+ reset ();
424428 try {
425429 setImpl (s, base);
426430 } catch (const std::exception& e) {
@@ -441,39 +445,42 @@ class EnumFlags
441445 }
442446
443447 // Resets a specific flag.
444- template <typename T>
445- requires std::is_same_v<T, E>
448+ template <std::same_as<E> T>
446449 constexpr void reset (T t)
447450 {
448451 mBits &= ~to_bit (t);
449452 }
450453
451454 // Tests if a specific flag is set.
452- template <typename T>
453- requires std::is_same_v<T, E>
455+ template <std::same_as<E> T>
454456 [[nodiscard]] constexpr bool test (T t) const noexcept
455457 {
456458 return (mBits & to_bit (t)) != None;
457459 }
458460
459461 // Tests if all specified flags are set.
460- template <typename ... Ts>
462+ template <std::same_as<E> ... Ts>
461463 [[nodiscard]] constexpr bool test (Ts... flags) const noexcept
462464 {
463465 return ((test (flags) && ...));
464466 }
465467
466468 // Sets a specific flag.
467- template <typename T>
468- requires std::is_same_v<T, E>
469+ template <std::same_as<E> T>
469470 constexpr void set (T t) noexcept
470471 {
471472 mBits |= to_bit (t);
472473 }
473474
475+ // Sets multiple specific flags.
476+ template <std::same_as<E>... Ts>
477+ constexpr void set (Ts... flags) noexcept
478+ {
479+ (set (flags), ...);
480+ }
481+
474482 // Toggles a specific flag.
475- template <typename T>
476- requires std::is_same_v<T, E>
483+ template <std::same_as<E> T>
477484 constexpr void toggle (T t) noexcept
478485 {
479486 mBits ^= to_bit (t);
@@ -538,8 +545,7 @@ class EnumFlags
538545 }
539546
540547 // Check if given flag is set.
541- template <typename T>
542- requires std::is_same_v<T, E>
548+ template <std::same_as<E> T>
543549 [[nodiscard]] constexpr bool operator [](const T t) const noexcept
544550 {
545551 return test (t);
@@ -564,26 +570,23 @@ class EnumFlags
564570 constexpr EnumFlags& operator =(EnumFlags&& o) = default ;
565571
566572 // Performs a bitwise OR with a flag.
567- template <typename T>
568- requires std::is_same_v<T, E>
573+ template <std::same_as<E> T>
569574 constexpr EnumFlags& operator |=(T t) noexcept
570575 {
571576 mBits |= to_bit (t);
572577 return *this ;
573578 }
574579
575580 // Performs a bitwise AND with a flag.
576- template <typename T>
577- requires std::is_same_v<T, E>
581+ template <std::same_as<E> T>
578582 constexpr EnumFlags& operator &=(T t) noexcept
579583 {
580584 mBits &= to_bit (t);
581585 return *this ;
582586 }
583587
584588 // Returns a flag set with a bitwise AND.
585- template <typename T>
586- requires std::is_same_v<T, E>
589+ template <std::same_as<E> T>
587590 constexpr EnumFlags operator &(T t) const noexcept
588591 {
589592 return EnumFlags (mBits & to_bit (t));
@@ -685,32 +688,89 @@ class EnumFlags
685688 // Set implementation, bits was zeroed before.
686689 void setImpl (const std::string& s, int base = 2 )
687690 {
691+ // Helper to check if character is valid for given base
692+ auto isValidForBase = [](unsigned char c, int base) -> bool {
693+ if (base == 2 ) {
694+ return c == ' 0' || c == ' 1' ;
695+ }
696+ if (base == 10 ) {
697+ return std::isdigit (c);
698+ }
699+ if (base == 16 ) {
700+ return std::isdigit (c) || (c >= ' a' && c <= ' f' ) || (c >= ' A' && c <= ' F' );
701+ }
702+ return false ;
703+ };
704+
705+ // hex
706+ if (base == 16 ) {
707+ std::string_view hex_str{s};
708+ // Strip optional 0x or 0X prefix
709+ if (s.size () >= 2 && s[0 ] == ' 0' && (s[1 ] == ' x' || s[1 ] == ' X' )) {
710+ hex_str.remove_prefix (2 );
711+ }
712+ if (hex_str.empty ()) {
713+ throw std::invalid_argument (" Empty hexadecimal string." );
714+ }
715+ if (!std::all_of (hex_str.begin (), hex_str.end (), [&](unsigned char c) { return isValidForBase (c, 16 ); })) {
716+ throw std::invalid_argument (" Invalid hexadecimal string." );
717+ }
718+ typename H::UMax v = std::stoul (std::string (hex_str), nullptr , 16 );
719+ if (v > H::MaxRep) {
720+ throw std::out_of_range (" Value exceeds enum range." );
721+ }
722+ mBits = static_cast <U>(v);
723+ return ;
724+ }
725+
726+ // decimal and binary
688727 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' ; })) {
728+ if (base == 2 ) {
729+ // Binary: check only 0 and 1
730+ if (!std::all_of (s.begin (), s.end (), [&](unsigned char c) { return isValidForBase (c, 2 ); })) {
691731 throw std::invalid_argument (" Invalid binary string." );
692732 }
693733 }
694- typename H::UMax v = std::stoul (s , nullptr , base);
734+ typename H::UMax v = std::stoul (std::string (s) , nullptr , base);
695735 if (v > H::MaxRep) {
696- throw std::out_of_range (" Values exceeds enum range." );
736+ throw std::out_of_range (" Value exceeds enum range." );
697737 }
698738 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 == ' ;' ; })) {
739+ }
740+ // enum name strings
741+ else if (std::all_of (s.begin (), s.end (), [](unsigned char c) { return std::isalnum (c) != 0 || c == ' |' || c == ' ' || c == ' :' || c == ' ,' || c == ' ;' ; })) {
700742 std::string cs{s};
701743 std::transform (cs.begin (), cs.end (), cs.begin (), [](unsigned char c) { return std::tolower (c); });
744+
702745 if (cs == H::All) {
703746 mBits = All;
704747 } else if (cs == H::None) {
705748 mBits = None;
706749 } else {
707- // accept as delimiter ' ', '|', ';', ','
750+ // Detect delimiter and ensure only one type is used
708751 char token = ' ' ;
709- std::string::size_type pos = s.find_first_of (" ,|;" );
710- if (pos != std::string::npos) {
711- token = s[pos];
752+ size_t pipePos = s.find (' |' );
753+ size_t commaPos = s.find (' ,' );
754+ size_t semiPos = s.find (' ;' );
755+
756+ // Count how many different delimiters exist
757+ int delimiterCount = (pipePos != std::string_view::npos ? 1 : 0 ) +
758+ (commaPos != std::string_view::npos ? 1 : 0 ) +
759+ (semiPos != std::string_view::npos ? 1 : 0 );
760+
761+ if (delimiterCount > 1 ) {
762+ throw std::invalid_argument (" Mixed delimiters not allowed!" );
763+ }
764+
765+ if (pipePos != std::string_view::npos) {
766+ token = ' |' ;
767+ } else if (commaPos != std::string_view::npos) {
768+ token = ' ,' ;
769+ } else if (semiPos != std::string_view::npos) {
770+ token = ' ;' ;
712771 }
713- for (const auto & tok : Str::tokenize (s, token)) {
772+
773+ for (const auto & tok : Str::tokenize (std::string (s), token)) {
714774 if (auto e = H::fromString (tok)) {
715775 mBits |= to_bit (*e);
716776 } else {
0 commit comments