Skip to content

Commit e747635

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

File tree

2 files changed

+454
-43
lines changed

2 files changed

+454
-43
lines changed

Common/Utils/include/CommonUtils/EnumFlags.h

Lines changed: 109 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {
355364
template <EnumFlag E>
356365
class 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

Comments
 (0)