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)));
+}