diff --git a/include/rsl/enum b/include/rsl/enum new file mode 100644 index 0000000..80d1dd5 --- /dev/null +++ b/include/rsl/enum @@ -0,0 +1,166 @@ +#pragma once +#include +#include +#include +#include + +namespace rsl { +inline namespace annotations { +struct FlagEnumTag {}; +constexpr inline FlagEnumTag flag_enum; + +} // namespace annotations +template +concept is_flag_enum = + std::is_scoped_enum_v && meta::has_annotation(^^T, ^^annotations::FlagEnumTag); + +template +constexpr bool has_flag(E flags, E needle) { + using U = std::underlying_type_t; + return (static_cast(flags) & static_cast(needle)) == static_cast(needle); +} + +template +constexpr bool has_flag(E flags, std::underlying_type_t needle) { + using U = std::underlying_type_t; + return (static_cast(flags) & needle) == needle; +} + +template + requires std::is_enum_v +constexpr bool is_named_enumerator(T value) { + constexpr static auto named_enumerators = + define_static_array(enumerators_of(^^T) | std::views::transform([](std::meta::info r) { + return extract(constant_of(r)); + })); + return std::ranges::contains(named_enumerators, value); +} +} // namespace rsl + +template +constexpr E operator~(E v) noexcept { + using U = std::underlying_type_t; + return static_cast(~static_cast(v)); +} + +template > T> +constexpr bool operator==(E lhs, T rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(lhs) == static_cast(rhs); +} + +template > T> +constexpr bool operator==(T lhs, E rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(lhs) == static_cast(rhs); +} + +template > T> +constexpr bool operator!=(E lhs, T rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(lhs) != static_cast(rhs); +} + +template > T> +constexpr bool operator!=(T lhs, E rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(lhs) != static_cast(rhs); +} + +template +constexpr E operator|(E lhs, E rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +template +constexpr E operator&(E lhs, E rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +template +constexpr E operator^(E lhs, E rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) ^ static_cast(rhs)); +} +template > T> +constexpr E operator|(E lhs, T rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +template > T> +constexpr E operator&(E lhs, T rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +template > T> +constexpr E operator^(E lhs, T rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) ^ static_cast(rhs)); +} + +template > T> +constexpr E operator|(T lhs, E rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +template > T> +constexpr E operator&(T lhs, E rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +template > T> +constexpr E operator^(T lhs, E rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) ^ static_cast(rhs)); +} + +template +constexpr E& operator|=(E& lhs, E rhs) noexcept { + return lhs = lhs | rhs; +} + +template +constexpr E& operator&=(E& lhs, E rhs) noexcept { + return lhs = lhs & rhs; +} + +template +constexpr E& operator^=(E& lhs, E rhs) noexcept { + return lhs = lhs ^ rhs; +} + +template > T> +constexpr E& operator|=(E& lhs, T rhs) noexcept { + return lhs = lhs | rhs; +} + +template > T> +constexpr E& operator&=(E& lhs, T rhs) noexcept { + return lhs = lhs & rhs; +} + +template > T> +constexpr E& operator^=(E& lhs, T rhs) noexcept { + return lhs = lhs ^ rhs; +} + +template > T> +constexpr T& operator|=(T& lhs, E rhs) noexcept { + return lhs = static_cast(lhs | rhs); +} + +template > T> +constexpr T& operator&=(T& lhs, E rhs) noexcept { + return lhs = static_cast(lhs & rhs); +} + +template > T> +constexpr T& operator^=(T& lhs, E rhs) noexcept { + return lhs = static_cast(lhs ^ rhs); +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ab12fa1..80ad2d2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,4 +9,5 @@ add_subdirectory(serializer) add_subdirectory(string_view) add_subdirectory(span) add_subdirectory(format) -add_subdirectory(kwargs) \ No newline at end of file +add_subdirectory(kwargs) +add_subdirectory(enum) \ No newline at end of file diff --git a/test/enum/CMakeLists.txt b/test/enum/CMakeLists.txt new file mode 100644 index 0000000..974001a --- /dev/null +++ b/test/enum/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(rsl-util-test PRIVATE + operator.cpp + utilities.cpp +) \ No newline at end of file diff --git a/test/enum/operator.cpp b/test/enum/operator.cpp new file mode 100644 index 0000000..0f59e8a --- /dev/null +++ b/test/enum/operator.cpp @@ -0,0 +1,92 @@ +#include +#include + +namespace { +enum class[[= rsl::flag_enum]] Permissions : uint8_t { + None = 0U, + Read = 1U << 0U, + Write = 1U << 1U, + Execute = 1U << 2U, + All = Read | Write | Execute +}; +} + +TEST(FlagEnum, HasFlag) { + ASSERT_TRUE(rsl::has_flag(Permissions::All, Permissions::Read)); + ASSERT_TRUE(rsl::has_flag(Permissions::All, Permissions::All)); + + ASSERT_TRUE(rsl::has_flag(Permissions::All, 1)); + ASSERT_TRUE(rsl::has_flag(Permissions::All, 0b11)); +} + +TEST(FlagEnum, Operators) { + // operator | + ASSERT_EQ(static_cast(Permissions::Read | Permissions::Write), 0b11); + ASSERT_EQ(static_cast(1 | Permissions::Write), 0b11); + ASSERT_EQ(static_cast(Permissions::Write | 1), 0b11); + + // operator & + ASSERT_TRUE(static_cast(Permissions::All & Permissions::Read) != 0); + ASSERT_TRUE(static_cast(Permissions::All & 1) != 0); + ASSERT_TRUE(static_cast(0b111 & Permissions::Read) != 0); + + // operator ^ + ASSERT_EQ(static_cast(Permissions::All ^ Permissions::Read), 0b110); + ASSERT_EQ(static_cast(Permissions::All ^ 1), 0b110); + ASSERT_EQ(static_cast(0b111 ^ Permissions::Read), 0b110); + + // operator == + ASSERT_TRUE(Permissions::Read == static_cast(1)); + ASSERT_TRUE(static_cast(1) == Permissions::Read); + + // operator != + ASSERT_TRUE(Permissions::Read != static_cast(2)); + ASSERT_TRUE(static_cast(2) != Permissions::Read); +} + +TEST(FlagEnum, CompoundOperator) { + // operator|= + { + Permissions p1 = Permissions::None; + Permissions p2 = Permissions::None; + uint8_t p3 = 0; + + p1 |= Permissions::Read; + p2 |= 1; + p3 |= Permissions::Read; + + ASSERT_EQ(p1, Permissions::Read); + ASSERT_EQ(p2, Permissions::Read); + ASSERT_EQ(p3, 1); + } + + // operator &= + { + Permissions p1 = Permissions::All; + Permissions p2 = Permissions::All; + uint8_t p3 = 0b111; + + p1 &= Permissions::Write; + p2 &= 1U << 1U; + p3 &= Permissions::Write; + + ASSERT_EQ(static_cast(p1), 0b10); + ASSERT_EQ(static_cast(p2), 0b10); + ASSERT_EQ(static_cast(p3), 0b10); + } + + // operator ^= + { + Permissions p1 = Permissions::All; + Permissions p2 = Permissions::All; + uint8_t p3 = 0b111; + + p1 ^= Permissions::Execute; + p2 ^= 1U << 2U; + p3 ^= Permissions::Execute; + + ASSERT_EQ(static_cast(p1), 0b11); + ASSERT_EQ(static_cast(p2), 0b11); + ASSERT_EQ(static_cast(p3), 0b11); + } +} \ No newline at end of file diff --git a/test/enum/utilities.cpp b/test/enum/utilities.cpp new file mode 100644 index 0000000..63e8395 --- /dev/null +++ b/test/enum/utilities.cpp @@ -0,0 +1,15 @@ +#include +#include + +namespace { +enum class Foo : uint8_t { + foo, + bar = 10 +}; +} + +TEST(Enum, IsNamed) { + ASSERT_TRUE(rsl::is_named_enumerator(Foo::bar)); + ASSERT_TRUE(rsl::is_named_enumerator(Foo::foo)); + ASSERT_FALSE(rsl::is_named_enumerator(Foo(20))); +}