Skip to content

Commit 2c6cb88

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

File tree

2 files changed

+444
-42
lines changed

2 files changed

+444
-42
lines changed

Common/Utils/include/CommonUtils/EnumFlags.h

Lines changed: 99 additions & 42 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.
@@ -161,7 +161,7 @@ struct FlagsHelper final {
161161
static constexpr auto Max_v{Values.back()}; // Enum last entry
162162
static constexpr auto Min_u_v{static_cast<size_t>(Min_v)}; // Enum first entry as size_t
163163
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");
164+
static_assert(Max_u_v < std::numeric_limits<U>::digits, "Max Bit is beyond allow range deferred from underlying type");
165165
static constexpr bool isContinuous() noexcept { return (Max_u_v - Min_u_v + 1) == count(); } // Is the enum continuous
166166
static constexpr UMax makeMaxRep(size_t min, size_t max)
167167
{
@@ -258,7 +258,7 @@ struct FlagsHelper final {
258258
static constexpr std::optional<E> fromString(std::string_view str) noexcept
259259
{
260260
for (size_t i{0}; i < count(); ++i) {
261-
if (Names[i] == str || NamesScoped[i] == str) {
261+
if (isIEqual(Names[i], str) || isIEqual(NamesScoped[i], str)) {
262262
return Values[i];
263263
}
264264
}
@@ -277,7 +277,7 @@ struct FlagsHelper final {
277277
return toLower(a) == toLower(b);
278278
}
279279

280-
// Case-insensitive comparision for string_view.
280+
// Case-insensitive comparison for string_view.
281281
static constexpr bool isIEqual(std::string_view s1, std::string_view s2) noexcept
282282
{
283283
if (s1.size() != s2.size()) {
@@ -294,7 +294,7 @@ struct FlagsHelper final {
294294
static constexpr std::string_view None{"none"};
295295
static constexpr bool hasNone() noexcept
296296
{
297-
// check that enum does not contain memeber named 'none'
297+
// check that enum does not contain member named 'none'
298298
for (size_t i{0}; i < count(); ++i) {
299299
if (isIEqual(Names[i], None)) {
300300
return true;
@@ -306,7 +306,7 @@ struct FlagsHelper final {
306306
static constexpr std::string_view All{"all"};
307307
static constexpr bool hasAll() noexcept
308308
{
309-
// check that enum does not contain memeber named 'all'
309+
// check that enum does not contain member named 'all'
310310
for (size_t i{0}; i < count(); ++i) {
311311
if (isIEqual(Names[i], All)) {
312312
return true;
@@ -332,7 +332,7 @@ concept EnumFlag = requires {
332332
};
333333

334334
/**
335-
* \brief Classs to aggregate and manage enum-based on-off flags.
335+
* \brief Class to aggregate and manage enum-based on-off flags.
336336
*
337337
* This class manages flags as bits in the underlying type of an enum (upto 64 bits), allowing
338338
* manipulation via enum member names. It supports operations akin to std::bitset
@@ -355,6 +355,7 @@ concept EnumFlag = requires {
355355
template <EnumFlag E>
356356
class EnumFlags
357357
{
358+
static constexpr int DefaultBase{2};
358359
using H = details::enum_flags::FlagsHelper<E>;
359360
using U = std::underlying_type_t<E>;
360361
U mBits{0};
@@ -388,9 +389,9 @@ class EnumFlags
388389
std::for_each(flags.begin(), flags.end(), [this](const E f) noexcept { mBits |= to_bit(f); });
389390
}
390391
// Init from a string.
391-
EnumFlags(const std::string& str)
392+
EnumFlags(std::string_view str, int base = DefaultBase)
392393
{
393-
set(str);
394+
set(str, base);
394395
}
395396
// Destructor.
396397
constexpr ~EnumFlags() = default;
@@ -413,14 +414,14 @@ class EnumFlags
413414
// Sets flags from a string representation.
414415
// This can be either from a number representation (binary or digits) or
415416
// a concatenation of the enums members name e.g., 'Enum1|Enum2|...'
416-
void set(const std::string& s = "", int base = 2)
417+
void set(std::string_view s, int base = DefaultBase)
417418
{
418-
// on throw restore previous state and rethrow
419-
const U prev = mBits;
420-
reset();
421419
if (s.empty()) { // no-op
422420
return;
423421
}
422+
// on throw restore previous state and rethrow
423+
const U prev = mBits;
424+
reset();
424425
try {
425426
setImpl(s, base);
426427
} catch (const std::exception& e) {
@@ -441,39 +442,42 @@ class EnumFlags
441442
}
442443

443444
// Resets a specific flag.
444-
template <typename T>
445-
requires std::is_same_v<T, E>
445+
template <std::same_as<E> T>
446446
constexpr void reset(T t)
447447
{
448448
mBits &= ~to_bit(t);
449449
}
450450

451451
// Tests if a specific flag is set.
452-
template <typename T>
453-
requires std::is_same_v<T, E>
452+
template <std::same_as<E> T>
454453
[[nodiscard]] constexpr bool test(T t) const noexcept
455454
{
456455
return (mBits & to_bit(t)) != None;
457456
}
458457

459458
// Tests if all specified flags are set.
460-
template <typename... Ts>
459+
template <std::same_as<E>... Ts>
461460
[[nodiscard]] constexpr bool test(Ts... flags) const noexcept
462461
{
463462
return ((test(flags) && ...));
464463
}
465464

466465
// Sets a specific flag.
467-
template <typename T>
468-
requires std::is_same_v<T, E>
466+
template <std::same_as<E> T>
469467
constexpr void set(T t) noexcept
470468
{
471469
mBits |= to_bit(t);
472470
}
473471

472+
// Sets multiple specific flags.
473+
template <std::same_as<E>... Ts>
474+
constexpr void set(Ts... flags) noexcept
475+
{
476+
(set(flags), ...);
477+
}
478+
474479
// Toggles a specific flag.
475-
template <typename T>
476-
requires std::is_same_v<T, E>
480+
template <std::same_as<E> T>
477481
constexpr void toggle(T t) noexcept
478482
{
479483
mBits ^= to_bit(t);
@@ -538,8 +542,7 @@ class EnumFlags
538542
}
539543

540544
// Check if given flag is set.
541-
template <typename T>
542-
requires std::is_same_v<T, E>
545+
template <std::same_as<E> T>
543546
[[nodiscard]] constexpr bool operator[](const T t) const noexcept
544547
{
545548
return test(t);
@@ -564,26 +567,23 @@ class EnumFlags
564567
constexpr EnumFlags& operator=(EnumFlags&& o) = default;
565568

566569
// Performs a bitwise OR with a flag.
567-
template <typename T>
568-
requires std::is_same_v<T, E>
570+
template <std::same_as<E> T>
569571
constexpr EnumFlags& operator|=(T t) noexcept
570572
{
571573
mBits |= to_bit(t);
572574
return *this;
573575
}
574576

575577
// Performs a bitwise AND with a flag.
576-
template <typename T>
577-
requires std::is_same_v<T, E>
578+
template <std::same_as<E> T>
578579
constexpr EnumFlags& operator&=(T t) noexcept
579580
{
580581
mBits &= to_bit(t);
581582
return *this;
582583
}
583584

584585
// Returns a flag set with a bitwise AND.
585-
template <typename T>
586-
requires std::is_same_v<T, E>
586+
template <std::same_as<E> T>
587587
constexpr EnumFlags operator&(T t) const noexcept
588588
{
589589
return EnumFlags(mBits & to_bit(t));
@@ -683,34 +683,91 @@ class EnumFlags
683683

684684
private:
685685
// Set implementation, bits was zeroed before.
686-
void setImpl(const std::string& s, int base = 2)
686+
void setImpl(std::string_view s, int base = 2)
687687
{
688+
// Helper to check if character is valid for given base
689+
auto isValidForBase = [](unsigned char c, int base) -> bool {
690+
if (base == 2) {
691+
return c == '0' || c == '1';
692+
}
693+
if (base == 10) {
694+
return std::isdigit(c);
695+
}
696+
if (base == 16) {
697+
return std::isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
698+
}
699+
return false;
700+
};
701+
702+
// hex
703+
if (base == 16) {
704+
std::string_view hex_str = s;
705+
// Strip optional 0x or 0X prefix
706+
if (s.size() >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
707+
hex_str = s.substr(2);
708+
}
709+
if (hex_str.empty()) {
710+
throw std::invalid_argument("Empty hexadecimal string.");
711+
}
712+
if (!std::all_of(hex_str.begin(), hex_str.end(), [&](unsigned char c) { return isValidForBase(c, 16); })) {
713+
throw std::invalid_argument("Invalid hexadecimal string.");
714+
}
715+
typename H::UMax v = std::stoul(std::string(hex_str), nullptr, 16);
716+
if (v > H::MaxRep) {
717+
throw std::out_of_range("Value exceeds enum range.");
718+
}
719+
mBits = static_cast<U>(v);
720+
return;
721+
}
722+
723+
// decimal and binary
688724
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'; })) {
725+
if (base == 2) {
726+
// Binary: check only 0 and 1
727+
if (!std::all_of(s.begin(), s.end(), [&](unsigned char c) { return isValidForBase(c, 2); })) {
691728
throw std::invalid_argument("Invalid binary string.");
692729
}
693730
}
694-
typename H::UMax v = std::stoul(s, nullptr, base);
731+
typename H::UMax v = std::stoul(std::string(s), nullptr, base);
695732
if (v > H::MaxRep) {
696-
throw std::out_of_range("Values exceeds enum range.");
733+
throw std::out_of_range("Value exceeds enum range.");
697734
}
698735
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 == ';'; })) {
736+
}
737+
// enum name strings
738+
else if (std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isalnum(c) != 0 || c == '|' || c == ' ' || c == ':' || c == ',' || c == ';'; })) {
700739
std::string cs{s};
701740
std::transform(cs.begin(), cs.end(), cs.begin(), [](unsigned char c) { return std::tolower(c); });
741+
702742
if (cs == H::All) {
703743
mBits = All;
704744
} else if (cs == H::None) {
705745
mBits = None;
706746
} else {
707-
// accept as delimiter ' ', '|', ';', ','
747+
// Detect delimiter and ensure only one type is used
708748
char token = ' ';
709-
std::string::size_type pos = s.find_first_of(",|;");
710-
if (pos != std::string::npos) {
711-
token = s[pos];
749+
size_t pipePos = s.find('|');
750+
size_t commaPos = s.find(',');
751+
size_t semiPos = s.find(';');
752+
753+
// Count how many different delimiters exist
754+
int delimiterCount = (pipePos != std::string_view::npos ? 1 : 0) +
755+
(commaPos != std::string_view::npos ? 1 : 0) +
756+
(semiPos != std::string_view::npos ? 1 : 0);
757+
758+
if (delimiterCount > 1) {
759+
throw std::invalid_argument("Mixed delimiters not allowed!");
712760
}
713-
for (const auto& tok : Str::tokenize(s, token)) {
761+
762+
if (pipePos != std::string_view::npos) {
763+
token = '|';
764+
} else if (commaPos != std::string_view::npos) {
765+
token = ',';
766+
} else if (semiPos != std::string_view::npos) {
767+
token = ';';
768+
}
769+
770+
for (const auto& tok : Str::tokenize(std::string(s), token)) {
714771
if (auto e = H::fromString(tok)) {
715772
mBits |= to_bit(*e);
716773
} else {

0 commit comments

Comments
 (0)