Skip to content

Commit 9685db0

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

File tree

2 files changed

+449
-44
lines changed

2 files changed

+449
-44
lines changed

Common/Utils/include/CommonUtils/EnumFlags.h

Lines changed: 104 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
5758
template <EnumFlagHelper E>
5859
struct 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 {
355357
template <EnumFlag E>
356358
class 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

Comments
 (0)