Skip to content

Commit 3ccbfe2

Browse files
committed
Common: EnumFlags add set
1 parent a5c604d commit 3ccbfe2

File tree

2 files changed

+462
-47
lines changed

2 files changed

+462
-47
lines changed

Common/Utils/include/CommonUtils/EnumFlags.h

Lines changed: 117 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {
355368
template <EnumFlag E>
356369
class 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

Comments
 (0)