diff --git a/Common/Utils/include/CommonUtils/EnumFlags.h b/Common/Utils/include/CommonUtils/EnumFlags.h index 9a8960f612553..4bd1a9e641056 100644 --- a/Common/Utils/include/CommonUtils/EnumFlags.h +++ b/Common/Utils/include/CommonUtils/EnumFlags.h @@ -57,6 +57,7 @@ concept EnumFlagHelper = requires { template struct FlagsHelper final { using U = std::underlying_type_t; + using UMax = uint64_t; // max represetable type static constexpr bool isScoped() noexcept { @@ -144,8 +145,8 @@ struct FlagsHelper final { { constexpr std::array valid{isValid(MinScan + I)>()...}; constexpr auto count{std::count_if(valid.cbegin(), valid.cend(), [](bool v) noexcept { return v; })}; - static_assert(count > 0, "Requiring non-empty enum!"); - static_assert(count <= MaxUnderScan, "Underlying type of enum has less digits than given expected!"); + static_assert(count > 0, "EnumFlag requires at least one enum value. Check that your enum has consecutive values starting from 0."); + static_assert(count <= MaxUnderScan, "Too many enum values for underlying type. Consider using a larger underlying type or fewer enum values."); std::array values{}; for (size_t idx{}, n{}; n < count; ++idx) { if (valid[idx]) { @@ -161,8 +162,16 @@ struct FlagsHelper final { static constexpr auto Min_u_v{static_cast(Min_v)}; // Enum first entry as size_t static constexpr auto Max_u_v{static_cast(Max_v)}; // Enum last entry as size_t static_assert(Max_u_v < std::numeric_limits::digits, "Max Bit is beyond allow range defered from underlying type"); - static constexpr bool isContinuous() noexcept { return (Max_u_v - Min_u_v + 1) == count(); } // Is the enum continuous - static constexpr auto MaxRep{((1ULL << (static_cast(Max_u_v - Min_u_v) + 1ULL)) - 1ULL) << Min_u_v}; // largest representable value + static constexpr bool isContinuous() noexcept { return (Max_u_v - Min_u_v + 1) == count(); } // Is the enum continuous + static constexpr UMax makeMaxRep(size_t min, size_t max) + { + const size_t width = max - min + 1; + if (width >= std::numeric_limits::digits) { + return std::numeric_limits::max(); + } + return ((UMax(1) << width) - 1) << min; + } + static constexpr auto MaxRep{makeMaxRep(Min_u_v, Max_u_v)}; // largest representable value template static constexpr std::string_view getName() @@ -173,7 +182,7 @@ struct FlagsHelper final { } if constexpr (tpeek_v[tp + getSpec().size()] == getSpec()) { #if defined __clang__ - if constexpr (tpeek_v[tp + getSpec().size() + 1] == getSpec()) { + if constexpr (tpeek_v[tp + getSpec().size() + 1] == getSpec()) { return {}; } #endif @@ -215,7 +224,7 @@ struct FlagsHelper final { template static constexpr auto getNameValue{getName()}; - template + template static constexpr auto getNames(std::index_sequence /*unused*/) { if constexpr (with_scope) { @@ -248,7 +257,7 @@ struct FlagsHelper final { static constexpr std::optional fromString(std::string_view str) noexcept { - for (std::size_t i{0}; i < count(); ++i) { + for (size_t i{0}; i < count(); ++i) { if (Names[i] == str || NamesScoped[i] == str) { return Values[i]; } @@ -325,7 +334,7 @@ concept EnumFlag = requires { /** * \brief Classs to aggregate and manage enum-based on-off flags. * - * This class manages flags as bits in the underlying type of an enum, allowing + * This class manages flags as bits in the underlying type of an enum (upto 64 bits), allowing * manipulation via enum member names. It supports operations akin to std::bitset * but is fully constexpr and is ideal for aggregating multiple on-off booleans, * e.g., enabling/disabling algorithm features. @@ -371,13 +380,18 @@ class EnumFlags constexpr EnumFlags(const EnumFlags&) = default; // Move constructor. constexpr EnumFlags(EnumFlags&&) = default; - // Constructor to initialize with the underlyiny type. + // Constructor to initialize with the underlying type. constexpr explicit EnumFlags(U u) : mBits(u) {} // Initialize with a list of flags. constexpr EnumFlags(std::initializer_list flags) noexcept { std::for_each(flags.begin(), flags.end(), [this](const E f) noexcept { mBits |= to_bit(f); }); } + // Init from a string. + EnumFlags(const std::string& str) + { + set(str); + } // Destructor. constexpr ~EnumFlags() = default; @@ -415,7 +429,7 @@ class EnumFlags } } // Returns the raw bitset value. - constexpr auto value() const noexcept + [[nodiscard]] constexpr auto value() const noexcept { return mBits; } @@ -442,6 +456,13 @@ class EnumFlags return (mBits & to_bit(t)) != None; } + // Tests if all specified flags are set. + template + [[nodiscard]] constexpr bool test(Ts... flags) const noexcept + { + return ((test(flags) && ...)); + } + // Sets a specific flag. template requires std::is_same_v @@ -464,6 +485,12 @@ class EnumFlags return mBits != None; } + // Checks if all flags are set. + [[nodiscard]] constexpr bool all() const noexcept + { + return mBits == All; + } + // Returns the bitset as a binary string. [[nodiscard]] std::string string() const { @@ -505,7 +532,7 @@ class EnumFlags } // Checks if any flag is set (Boolean context). - constexpr explicit operator bool() const noexcept + [[nodiscard]] constexpr explicit operator bool() const noexcept { return any(); } @@ -513,19 +540,19 @@ class EnumFlags // Check if given flag is set. template requires std::is_same_v - constexpr bool operator[](const T t) noexcept + [[nodiscard]] constexpr bool operator[](const T t) const noexcept { return test(t); } // Checks if two flag sets are equal. - constexpr bool operator==(const EnumFlags& o) const noexcept + [[nodiscard]] constexpr bool operator==(const EnumFlags& o) const noexcept { return mBits == o.mBits; } // Checks if two flag sets are not equal. - constexpr bool operator!=(const EnumFlags& o) const noexcept + [[nodiscard]] constexpr bool operator!=(const EnumFlags& o) const noexcept { return mBits != o.mBits; } @@ -584,7 +611,13 @@ class EnumFlags // Performs a bitwise XOR with another flag set. constexpr EnumFlags operator^(const EnumFlags& o) const noexcept { - return Flags(mBits ^ o.mBits); + return EnumFlags(mBits ^ o.mBits); + } + + // Performs a bitwise and with another flag set. + constexpr EnumFlags operator&(const EnumFlags& o) const noexcept + { + return EnumFlags(mBits & o.mBits); } // Performs a bitwise XOR assignment. @@ -596,14 +629,14 @@ class EnumFlags // Checks if all specified flags are set. template - constexpr bool all_of(Ts... flags) const noexcept + [[nodiscard]] constexpr bool all_of(Ts... flags) const noexcept { - return ((test(flags) && ...)); + return test(flags...); } // Checks if none of the specified flags are set. template - constexpr bool none_of(Ts... flags) const noexcept + [[nodiscard]] constexpr bool none_of(Ts... flags) const noexcept { return (!(test(flags) || ...)); } @@ -617,7 +650,7 @@ class EnumFlags // Deserializes a string into the flag set. void deserialize(const std::string& data) { - uint64_t v = std::stoul(data); + typename H::UMax v = std::stoul(data); if (v > H::MaxRep) { throw std::out_of_range("Values exceeds enum range."); } @@ -627,35 +660,29 @@ class EnumFlags // Counts the number of set bits (active flags). [[nodiscard]] constexpr size_t count() const noexcept { - size_t c{0}; - for (size_t i{H::Min_u_v}; i < H::Max_u_v; ++i) { - if ((mBits & (U(1) << i)) != U(0)) { - ++c; - } - } - return c; + return std::popcount(mBits); } // Returns the union of two flag sets. - constexpr EnumFlags union_with(const EnumFlags& o) const noexcept + [[nodiscard]] constexpr EnumFlags union_with(const EnumFlags& o) const noexcept { return EnumFlags(mBits | o.mBits); } // Returns the intersection of two flag sets. - constexpr EnumFlags intersection_with(const EnumFlags& o) const noexcept + [[nodiscard]] constexpr EnumFlags intersection_with(const EnumFlags& o) const noexcept { return EnumFlags(mBits & o.mBits); } // Checks if all flags in another Flags object are present in the current object. - constexpr bool contains(const EnumFlags& other) const noexcept + [[nodiscard]] constexpr bool contains(const EnumFlags& other) const noexcept { return (mBits & other.mBits) == other.mBits; } private: - // Set implemnetation, bits was zeroed before. + // Set implementation, bits was zeroed before. void setImpl(const std::string& s, int base = 2) { if (std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isdigit(c); })) { @@ -664,12 +691,12 @@ class EnumFlags throw std::invalid_argument("Invalid binary string."); } } - uint64_t v = std::stoul(s, nullptr, base); + typename H::UMax v = std::stoul(s, nullptr, base); if (v > H::MaxRep) { throw std::out_of_range("Values exceeds enum range."); } mBits = static_cast(v); - } else if (std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isalnum(c) != 0 || c == '|' || c == ' ' || c == ':' || c == ','; })) { + } else if (std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isalnum(c) != 0 || c == '|' || c == ' ' || c == ':' || c == ',' || c == ';'; })) { std::string cs{s}; std::transform(cs.begin(), cs.end(), cs.begin(), [](unsigned char c) { return std::tolower(c); }); if (cs == H::All) { @@ -677,7 +704,12 @@ class EnumFlags } else if (cs == H::None) { mBits = None; } else { - char token = (s.find(',') != std::string::npos) ? ',' : '|'; + // accept as delimiter ' ', '|', ';', ',' + char token = ' '; + std::string::size_type pos = s.find_first_of(",|;"); + if (pos != std::string::npos) { + token = s[pos]; + } for (const auto& tok : Str::tokenize(s, token)) { if (auto e = H::fromString(tok)) { mBits |= to_bit(*e); diff --git a/Common/Utils/test/testEnumFlags.cxx b/Common/Utils/test/testEnumFlags.cxx index 41b43bc4218ff..80f85c847653b 100644 --- a/Common/Utils/test/testEnumFlags.cxx +++ b/Common/Utils/test/testEnumFlags.cxx @@ -14,6 +14,9 @@ #define BOOST_TEST_DYN_LINK #include +#include +#include + #include #include @@ -21,7 +24,7 @@ // Example enum to use with EnumFlags enum class TestEnum : uint8_t { - Bit1, + Bit1 = 0, Bit2, Bit3, Bit4, @@ -29,44 +32,16 @@ enum class TestEnum : uint8_t { }; // Very long enum -// to test that it works beyond 32 bits +// to test that it works beyond 32 bits upto 64 bits +#define ENUM_BIT_NAME(n) Bit##n +#define ENUM_BIT_NAME_EXPAND(n) ENUM_BIT_NAME(n) +#define ENUM_BIT(z, n, _) ENUM_BIT_NAME_EXPAND(BOOST_PP_INC(n)) = (n), enum class TestEnumLong : uint64_t { - Bit1, - Bit2, - Bit3, - Bit4, - Bit5, - Bit6, - Bit7, - Bit8, - Bit9, - Bit10, - Bit11, - Bit12, - Bit13, - Bit14, - Bit15, - Bit16, - Bit17, - Bit18, - Bit19, - Bit20, - Bit21, - Bit22, - Bit23, - Bit24, - Bit25, - Bit26, - Bit27, - Bit28, - Bit29, - Bit30, - Bit31, - Bit32, - Bit33, - Bit34, - // ... + BOOST_PP_REPEAT(64, ENUM_BIT, _) }; +#undef ENUM_BIT +#undef ENUM_BIT_NAME +#undef ENUM_BIT_NAME_EXPAND BOOST_AUTO_TEST_CASE(Flags_test) { @@ -181,7 +156,7 @@ BOOST_AUTO_TEST_CASE(Flags_test) BOOST_TEST(flags.test(TestEnum::Bit4)); } - { // test with different delimiter + { // test with , delimiter std::string str = "Bit4,TestEnum::Bit2 , Bit1 "; flags.set(str); BOOST_TEST(flags.test(TestEnum::Bit1)); @@ -190,6 +165,15 @@ BOOST_AUTO_TEST_CASE(Flags_test) BOOST_TEST(flags.test(TestEnum::Bit4)); } + { // test with ; delimiter + std::string str = "Bit4;TestEnum::Bit2 ; Bit1 "; + flags.set(str); + BOOST_TEST(flags.test(TestEnum::Bit1)); + BOOST_TEST(flags.test(TestEnum::Bit2)); + BOOST_TEST(!flags.test(TestEnum::Bit3)); + BOOST_TEST(flags.test(TestEnum::Bit4)); + } + { // throw test with mixed delimiter std::string str = "Bit4|TestEnum::Bit2 , Bit1 "; BOOST_CHECK_THROW(flags.set(str), std::invalid_argument); @@ -275,6 +259,14 @@ BOOST_AUTO_TEST_CASE(Flags_test) EFlags flags3{TestEnum::Bit1, TestEnum::Bit2, TestEnum::Bit3}; EFlags flags4{TestEnum::Bit2, TestEnum::Bit3, TestEnum::Bit4}; + // test xor + auto flagsXOR = flags3 ^ flags4; + BOOST_CHECK(flagsXOR.test(TestEnum::Bit1, TestEnum::Bit4)); + + // test and + auto flagsAND = flags3 & flags4; + BOOST_CHECK(flagsAND.test(TestEnum::Bit2, TestEnum::Bit3)); + // Perform an intersection operation EFlags intersectionFlags = flags3.intersection_with(flags4); BOOST_CHECK(intersectionFlags.test(TestEnum::Bit2)); @@ -284,6 +276,14 @@ BOOST_AUTO_TEST_CASE(Flags_test) BOOST_CHECK_EQUAL(intersectionFlags.value(), 6); // 0110 in binary } + { + // Check special flag names. + EFlags flag("all"); + BOOST_CHECK(flag.all()); + flag.set("none"); + BOOST_CHECK(!flag.any()); + } + { // Create two flag sets EFlags flags1{TestEnum::Bit1, TestEnum::Bit2, TestEnum::Bit3}; @@ -300,8 +300,9 @@ BOOST_AUTO_TEST_CASE(Flags_test) { // Test compilation using an enum with more than 32 bits - o2::utils::EnumFlags test; - test.set("Bit32"); - BOOST_CHECK(test.test(TestEnumLong::Bit32)); + // Also tests space delimiter and construction from string. + o2::utils::EnumFlags test("Bit32 Bit34"); + BOOST_CHECK(test.test(TestEnumLong::Bit32, TestEnumLong::Bit34)); + BOOST_CHECK(!test.test(TestEnumLong::Bit1, TestEnumLong::Bit23)); } }