From deb4401036ac6f03b9f96cb5f2dfa851e3b61be0 Mon Sep 17 00:00:00 2001 From: nerix Date: Tue, 16 Jul 2024 16:35:44 +0200 Subject: [PATCH] chore: make `FlagsEnum` constexpr (#5510) --- CHANGELOG.md | 1 + src/PrecompiledHeader.hpp | 1 + src/common/FlagsEnum.hpp | 119 ++++++++----- tests/CMakeLists.txt | 1 + tests/src/FlagsEnum.cpp | 345 ++++++++++++++++++++++++++++++++++++++ tests/src/Test.hpp | 4 +- 6 files changed, 425 insertions(+), 46 deletions(-) create mode 100644 tests/src/FlagsEnum.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ace840d..f3d0e1f0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ - Dev: Cleanup `BrowserExtension`. (#5465) - Dev: Deprecate Qt 5.12. (#5396) - Dev: The running Qt version is now shown in the about page if it differs from the compiled version. (#5501) +- Dev: `FlagsEnum` is now `constexpr`. (#5510) ## 2.5.1 diff --git a/src/PrecompiledHeader.hpp b/src/PrecompiledHeader.hpp index 144faf56b..5a66ec73f 100644 --- a/src/PrecompiledHeader.hpp +++ b/src/PrecompiledHeader.hpp @@ -108,6 +108,7 @@ # include # include # include +# include # include # include # include diff --git a/src/common/FlagsEnum.hpp b/src/common/FlagsEnum.hpp index 7d1c11c8a..e49c30aa6 100644 --- a/src/common/FlagsEnum.hpp +++ b/src/common/FlagsEnum.hpp @@ -1,59 +1,74 @@ #pragma once -#include +#include #include namespace chatterino { -template ::type> +template + requires std::is_enum_v class FlagsEnum { public: - FlagsEnum() - : value_(static_cast(0)) + using Int = std::underlying_type_t; + + constexpr FlagsEnum() noexcept = default; + + constexpr FlagsEnum(std::convertible_to auto... flags) noexcept + : value_( + static_cast((static_cast(static_cast(flags)) | ...))) { } - FlagsEnum(T value) - : value_(value) + friend constexpr bool operator==(FlagsEnum lhs, FlagsEnum rhs) noexcept { + return lhs.value_ == rhs.value_; + } + friend constexpr bool operator!=(FlagsEnum lhs, FlagsEnum rhs) noexcept + { + return lhs.value_ != rhs.value_; } - FlagsEnum(std::initializer_list flags) + friend constexpr bool operator==(FlagsEnum lhs, T rhs) noexcept { - for (auto flag : flags) - { - this->set(flag); - } + return lhs.value_ == rhs; + } + friend constexpr bool operator!=(FlagsEnum lhs, T rhs) noexcept + { + return lhs.value_ != rhs; } - bool operator==(const FlagsEnum &other) const + friend constexpr bool operator==(T lhs, FlagsEnum rhs) noexcept { - return this->value_ == other.value_; + return lhs == rhs.value_; + } + friend constexpr bool operator!=(T lhs, FlagsEnum rhs) noexcept + { + return lhs != rhs.value_; } - bool operator!=(const FlagsEnum &other) const + constexpr void set(std::convertible_to auto... flags) noexcept { - return this->value_ != other.value_; - } - - void set(T flag) - { - reinterpret_cast(this->value_) |= static_cast(flag); + this->value_ = + static_cast(static_cast(this->value_) | + (static_cast(static_cast(flags)) | ...)); } /** Adds the flags from `flags` in this enum. */ - void set(FlagsEnum flags) + constexpr void set(FlagsEnum flags) noexcept { - reinterpret_cast(this->value_) |= static_cast(flags.value_); + this->value_ = static_cast(static_cast(this->value_) | + static_cast(flags.value_)); } - void unset(T flag) + constexpr void unset(std::convertible_to auto... flags) noexcept { - reinterpret_cast(this->value_) &= ~static_cast(flag); + this->value_ = + static_cast(static_cast(this->value_) & + ~(static_cast(static_cast(flags)) | ...)); } - void set(T flag, bool value) + constexpr void set(T flag, bool value) noexcept { if (value) { @@ -65,43 +80,59 @@ public: } } - bool has(T flag) const + constexpr FlagsEnum operator|(T flag) const noexcept { - return static_cast(this->value_) & static_cast(flag); + return static_cast(static_cast(this->value_) | + static_cast(flag)); } - FlagsEnum operator|(T flag) + constexpr FlagsEnum operator|(FlagsEnum rhs) const noexcept { - FlagsEnum xd; - xd.value_ = this->value_; - xd.set(flag, true); - - return xd; + return static_cast(static_cast(this->value_) | + static_cast(rhs.value_)); } - FlagsEnum operator|(FlagsEnum rhs) + constexpr bool has(T flag) const noexcept { - return static_cast(static_cast(this->value_) | - static_cast(rhs.value_)); + return static_cast(this->value_) & static_cast(flag); } - bool hasAny(FlagsEnum flags) const + constexpr bool hasAny(FlagsEnum flags) const noexcept { - return static_cast(this->value_) & static_cast(flags.value_); + return (static_cast(this->value_) & + static_cast(flags.value_)) != 0; } - bool hasAll(FlagsEnum flags) const + constexpr bool hasAny(std::convertible_to auto... flags) const noexcept { - return (static_cast(this->value_) & static_cast(flags.value_)) && - static_cast(flags->value); + return this->hasAny(FlagsEnum{flags...}); } - bool hasNone(std::initializer_list flags) const + constexpr bool hasAll(FlagsEnum flags) const noexcept { - return !this->hasAny(flags); + return (static_cast(this->value_) & + static_cast(flags.value_)) == + static_cast(flags.value_); } - T value() const + constexpr bool hasAll(std::convertible_to auto... flags) const noexcept + { + return this->hasAll(FlagsEnum{flags...}); + } + + constexpr bool hasNone(FlagsEnum flags) const noexcept + { + return (static_cast(this->value_) & + static_cast(flags.value_)) == 0; + } + + constexpr bool hasNone() const noexcept = delete; + constexpr bool hasNone(std::convertible_to auto... flags) const noexcept + { + return this->hasNone(FlagsEnum{flags...}); + } + + constexpr T value() const noexcept { return this->value_; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 19ac9195b..79c490fa9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -45,6 +45,7 @@ set(test_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/ModerationAction.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Scrollbar.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Commands.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/FlagsEnum.cpp # Add your new file above this line! ) diff --git a/tests/src/FlagsEnum.cpp b/tests/src/FlagsEnum.cpp new file mode 100644 index 000000000..4b3e94ba5 --- /dev/null +++ b/tests/src/FlagsEnum.cpp @@ -0,0 +1,345 @@ +#include "common/FlagsEnum.hpp" + +#include "Test.hpp" + +using namespace chatterino; + +namespace { + +enum class BasicScoped : std::uint16_t { + None = 0, + Foo = 1 << 0, + Bar = 1 << 1, + Baz = 1 << 2, + Qox = 1 << 3, + Quux = 1 << 4, + Corge = 1 << 5, + Grault = 1 << 6, + Garply = 1 << 7, + Waldo = 1 << 8, + Fred = 1 << 9, +}; +using BasicInt = std::underlying_type_t; + +enum BasicUnscoped : BasicInt { + None = 0, + Foo = 1 << 0, + Bar = 1 << 1, + Baz = 1 << 2, + Qox = 1 << 3, + Quux = 1 << 4, + Corge = 1 << 5, + Grault = 1 << 6, + Garply = 1 << 7, + Waldo = 1 << 8, + Fred = 1 << 9, +}; + +using Scoped = FlagsEnum; +using Unscoped = FlagsEnum; + +} // namespace + +TEST(FlagsEnum, sizeAndAlign) +{ + enum class U8 : std::uint8_t {}; + enum class I8 : std::int8_t {}; + enum class U16 : std::uint16_t {}; + enum class I16 : std::int16_t {}; + enum class U32 : std::uint32_t {}; + enum class I32 : std::int32_t {}; + enum class U64 : std::uint64_t {}; + enum class I64 : std::int64_t {}; + + auto check = []() { + return (((sizeof(T) == sizeof(FlagsEnum)) && ...) && + ((alignof(T) == alignof(FlagsEnum)) && ...)); + }; + + static_assert( + check.template operator()()); +} + +template +consteval void testCtor() +{ + using FE = FlagsEnum; + using U = std::underlying_type_t; + + static_assert(FE{}.value() == E::None); + static_assert(FE{E::Bar}.value() == E::Bar); + static_assert( + FE{E::Bar, E::Qox}.value() == + static_cast(static_cast(E::Bar) | static_cast(E::Qox))); + static_assert( + FE{E::Bar, E::Bar, E::Qox}.value() == + static_cast(static_cast(E::Bar) | static_cast(E::Qox))); +} + +TEST(FlagsEnum, ctor) +{ + testCtor(); + testCtor(); +} + +template +consteval void testOperatorEq() +{ + using FE = FlagsEnum; + + static_assert(FE{} == FE{}); + + static_assert(FE{E::Corge} == FE{E::Corge}); + + static_assert(FE{E::Corge, E::Garply} == FE{E::Garply, E::Corge}); + + static_assert(FE{} == E::None); + static_assert(E::None == FE{}); + static_assert(FE{E::Foo} == E::Foo); + static_assert(E::Foo == FE{E::Foo}); +} + +TEST(FlagsEnum, operatorEq) +{ + testOperatorEq(); + testOperatorEq(); +} + +template +consteval void testOperatorNeq() +{ + using FE = FlagsEnum; + + static_assert(FE{} != FE{E::Quux}); + + static_assert(FE{E::Corge} != FE{E::Grault}); + + static_assert(FE{E::Corge, E::Garply} != FE{E::Garply, E::Waldo}); + + static_assert(FE{} != E::Foo); + static_assert(E::Foo != FE{}); + static_assert(FE{E::Foo} != E::None); + static_assert(E::None != FE{E::Foo}); +} + +TEST(FlagsEnum, operatorNeq) +{ + testOperatorNeq(); + testOperatorNeq(); +} + +template +inline void testSetUnset() +{ + using FE = FlagsEnum; + + FE s; + ASSERT_EQ(s, FE{}); + s.set(E::Foo); + ASSERT_EQ(s, E::Foo); + s.set(E::Fred, E::Qox); + ASSERT_EQ(s, (FE{E::Foo, E::Fred, E::Qox})); + s.set(E::Foo); + ASSERT_EQ(s, (FE{E::Foo, E::Fred, E::Qox})); + + s.set(FE{E::Bar, E::Baz, E::Qox}); + ASSERT_EQ(s, (FE{E::Foo, E::Fred, E::Qox, E::Bar, E::Baz})); + + s.set(E::Foo, false); + ASSERT_EQ(s, (FE{E::Fred, E::Qox, E::Bar, E::Baz})); + + s.unset(E::Foo, E::Qox, E::Bar); + ASSERT_EQ(s, (FE{E::Fred, E::Baz})); + + s.unset(E::Fred, E::Baz, E ::Bar); + ASSERT_EQ(s, FE{}); + + s.unset(E::Baz); + ASSERT_EQ(s, FE{}); + + static_assert([] { + FE s; + bool result = s == FE{}; + + s.set(E::Foo); + result = result && s == E::Foo; + + s.set(E::Fred, E::Qox); + result = result && s == FE{E::Foo, E::Fred, E::Qox}; + + s.set(E::Foo); + result = result && s == FE{E::Foo, E::Fred, E::Qox}; + + s.set(FE{E::Bar, E::Baz, E::Qox}); + result = result && s == FE{E::Foo, E::Fred, E::Qox, E::Bar, E::Baz}; + + s.set(E::Foo, false); + result = result && s == (FE{E::Fred, E::Qox, E::Bar, E::Baz}); + + s.unset(E::Foo, E::Qox, E::Bar); + result = result && s == (FE{E::Fred, E::Baz}); + + s.unset(E::Fred, E::Baz, E ::Bar); + result = result && s == (FE{}); + + s.unset(E::Baz); + result = result && s == (FE{}); + + return result; + }()); +} + +TEST(FlagsEnum, setUnset) +{ + testSetUnset(); + testSetUnset(); +} + +template +consteval void testOperatorBitOr() +{ + using FE = FlagsEnum; + + static_assert((FE{E::Foo} | E::Bar) == FE{E::Foo, E::Bar}); + static_assert((FE{E::Foo} | E::Foo) == FE{E::Foo}); + static_assert((FE{E::Foo} | E::None) == FE{E::Foo}); + static_assert((FE{} | E::Foo) == FE{E::Foo}); + static_assert((FE{} | E::None) == FE{}); + + static_assert((FE{E::Foo} | FE{E::Bar}) == FE{E::Foo, E::Bar}); + static_assert((FE{E::Foo} | FE{E::Foo, E::Bar, E::Baz}) == + FE{E::Foo, E::Bar, E::Baz}); + static_assert((FE{E::Foo} | FE{}) == FE{E::Foo}); + static_assert((FE{} | FE{}) == FE{}); +} + +TEST(FlagsEnum, operatorBitOr) +{ + testOperatorBitOr(); + testOperatorBitOr(); +} + +template +consteval void testHas() +{ + using FE = FlagsEnum; + + static_assert(FE{E::Foo}.has(E::Foo)); + static_assert(!FE{E::Foo}.has(E::Bar)); + static_assert(!FE{E::Foo}.has(E::None)); + + static_assert(!FE{}.has(E::Foo)); + static_assert(!FE{}.has(E::None)); + + static_assert(FE{E::Qox, E::Waldo, E::Garply}.has(E::Qox)); + static_assert(FE{E::Qox, E::Waldo, E::Garply}.has(E::Waldo)); + static_assert(FE{E::Qox, E::Waldo, E::Garply}.has(E::Garply)); + static_assert(!FE{E::Qox, E::Waldo, E::Garply}.has(E::Grault)); +} + +TEST(FlagsEnum, has) +{ + testHas(); + testHas(); +} + +template +consteval void testHasAny() +{ + using FE = FlagsEnum; + + static_assert(FE{E::Foo}.hasAny(E::Foo)); + static_assert(!FE{E::Foo}.hasAny(E::Bar)); + static_assert(!FE{E::Foo}.hasAny(E::None)); + + static_assert(!FE{}.hasAny(E::Foo)); + static_assert(!FE{}.hasAny(E::None)); + + static_assert(FE{E::Qox, E::Waldo, E::Garply}.hasAny(E::Qox, E::Foo)); + static_assert(FE{E::Qox, E::Waldo, E::Garply}.hasAny({E::Qox, E::Foo})); + static_assert(FE{E::Qox, E::Waldo, E::Garply}.hasAny(E::Waldo, E::None)); + static_assert(FE{E::Qox, E::Waldo, E::Garply}.hasAny(E::Garply, E::Grault)); + static_assert(!FE{E::Qox, E::Waldo, E::Garply}.hasAny(E::Grault)); + static_assert(!FE{E::Qox, E::Waldo, E::Garply}.hasAny()); +} + +TEST(FlagsEnum, hasAny) +{ + testHasAny(); + testHasAny(); +} + +template +consteval void testHasAll() +{ + using FE = FlagsEnum; + + static_assert(FE{E::Foo}.hasAll(E::Foo)); + static_assert(FE{E::Foo}.hasAll(E::None)); + static_assert(!FE{E::Foo}.hasAll(E::Bar)); + + static_assert(FE{}.hasAll(E::None)); + static_assert(!FE{}.hasAll(E::Foo)); + + static_assert(FE{E::Qox, E::Waldo, E::Garply}.hasAll(E::Waldo, E::None)); + static_assert(FE{E::Qox, E::Waldo, E::Garply}.hasAll({E::Waldo, E::None})); + static_assert(FE{E::Qox, E::Waldo, E::Garply}.hasAll()); + static_assert( + FE{E::Qox, E::Waldo, E::Garply}.hasAll(E::Qox, E::Waldo, E::Garply)); + static_assert(FE{E::Qox, E::Waldo, E::Garply}.hasAll(E::Qox, E::Waldo, + E::Garply, E::None)); + + static_assert(!FE{E::Qox, E::Waldo, E::Garply}.hasAll(E::Qox, E::Foo)); + static_assert( + !FE{E::Qox, E::Waldo, E::Garply}.hasAll(E::Garply, E::Grault)); + static_assert(!FE{E::Qox, E::Waldo, E::Garply}.hasAll(E::Grault)); + static_assert(!FE{E::Qox, E::Waldo, E::Garply}.hasAll(E::Qox, E::Waldo, + E::Garply, E::Foo)); +} + +TEST(FlagsEnum, hasAll) +{ + testHasAll(); + testHasAll(); +} + +template +consteval void testHasNone() +{ + using FE = FlagsEnum; + + static_assert(FE{E::Foo}.hasNone(E::Bar)); + static_assert(FE{E::Foo}.hasNone(E::None)); + + static_assert(FE{}.hasNone(E::Foo)); + static_assert(FE{}.hasNone(E::None)); + + static_assert(FE{E::Qox, E::Waldo, E::Garply}.hasNone(E::Foo, E::Foo)); + static_assert(FE{E::Qox, E::Waldo, E::Garply}.hasNone({E::Bar, E::Foo})); + static_assert(!FE{E::Qox, E::Waldo, E::Garply}.hasNone(E::Waldo, E::Foo)); + static_assert(!FE{E::Qox, E::Waldo, E::Garply}.hasNone(E::Garply)); +} + +TEST(FlagsEnum, hasNone) +{ + testHasNone(); + testHasNone(); +} + +template +constexpr inline auto CONSTRUCTION_VALID = requires() { FlagsEnum{}; }; + +TEST(FlagsEnum, templateParam) +{ + struct S { + }; + + static_assert(!CONSTRUCTION_VALID); + static_assert(!CONSTRUCTION_VALID); + static_assert(!CONSTRUCTION_VALID); + static_assert(!CONSTRUCTION_VALID); + static_assert(!CONSTRUCTION_VALID); + + static_assert(CONSTRUCTION_VALID); + static_assert(CONSTRUCTION_VALID); +} diff --git a/tests/src/Test.hpp b/tests/src/Test.hpp index 064f90c6d..a44e244a7 100644 --- a/tests/src/Test.hpp +++ b/tests/src/Test.hpp @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include // IWYU pragma: export +#include // IWYU pragma: export #include