diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4dd0a8a..62533c6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,6 +32,7 @@ FILES yycc/macro/endian_detector.hpp yycc/macro/compiler_detector.hpp yycc/macro/class_copy_move.hpp + yycc/flag_enum.hpp yycc/string/reinterpret.hpp yycc/string/op.hpp yycc/num/parse.hpp diff --git a/src/yycc/flag_enum.hpp b/src/yycc/flag_enum.hpp new file mode 100644 index 0000000..4d3ca89 --- /dev/null +++ b/src/yycc/flag_enum.hpp @@ -0,0 +1,195 @@ +#pragma once +#include + +/** + * @brief The namespace for convenient C++ enum class logic operations. + * @details + * C++ enum class statement is a modern way to declare enum in C++. + * But it lack essential logic operations which is commonly used by programmer. + * So we create this helper to resolve this issue. +*/ +namespace yycc::flag_enum { + + // YYC MARK: + // + // Reference: + // Enum operator overload: https://stackoverflow.com/a/71107019 + // Constexpr operator overload: https://stackoverflow.com/a/17746099 + // + // Currently, the solution of "Constexpr operator overload" is not used. + // We use explicit way, "Enum operator overload". + + // template, int> = 0> + // inline constexpr TEnum operator|(TEnum lhs, TEnum rhs) { + // using ut = std::underlying_type_t; + // return static_cast(static_cast(lhs) | static_cast(rhs)); + // } + // template, int> = 0> + // inline constexpr TEnum operator|=(TEnum& lhs, TEnum rhs) { + // using ut = std::underlying_type_t; + // lhs = lhs | rhs; + // return lhs; + // } + + // template, int> = 0> + // inline constexpr TEnum operator&(TEnum lhs, TEnum rhs) { + // using ut = std::underlying_type_t; + // return static_cast(static_cast(lhs) & static_cast(rhs)); + // } + // template, int> = 0> + // inline constexpr TEnum operator&=(TEnum& lhs, TEnum rhs) { + // using ut = std::underlying_type_t; + // lhs = lhs & rhs; + // return lhs; + // } + + // template, int> = 0> + // inline constexpr TEnum operator^(TEnum lhs, TEnum rhs) { + // using ut = std::underlying_type_t; + // return static_cast(static_cast(lhs) ^ static_cast(rhs)); + // } + // template, int> = 0> + // inline constexpr TEnum operator^=(TEnum& lhs, TEnum rhs) { + // using ut = std::underlying_type_t; + // lhs = lhs ^ rhs; + // return lhs; + // } + + // template, int> = 0> + // inline constexpr TEnum operator~(TEnum lhs) { + // using ut = std::underlying_type_t; + // return static_cast(~(static_cast(lhs))); + // } + + // template, int> = 0> + // inline constexpr bool operator bool(TEnum lhs) { + // using ut = std::underlying_type_t; + // return static_cast(static_cast(lhs)); + // } + + /** + * @private + * @brief The helper struct to check all given template argument are the same enum type. + * @tparam TEnum The template parameter to be checked (first one). + * @tparam Ts The template parameter to be checked. + */ + template + struct AllSameEnum { + public: + // YYC MARK: + // Please note that we must use std::is_same, not std::is_same_v! + // That's std::conjunction_v required. + static constexpr bool value = std::is_enum_v> + && std::conjunction_v, std::remove_cv_t>...>; + }; + /** + * @private + * @brief The convenient calling to all_enum_values::value to check enum template parameter. + * @tparam TEnum The template parameter to be checked (first one). + * @tparam Ts The template parameter to be checked. + */ + template + inline constexpr bool ALL_SAME_ENUM = AllSameEnum::value; + + /** + * @brief Merge given enum flags like performing e1 | e2 | ... | en + * @tparam TEnum Enum type for processing. + * @param[in] val The first enum flag to be merged. + * @param[in] val_left Left enum flags to be merged. + * @return The merged enum flag. + * @remarks + * This function use recursive expansion to get final merge result. + * So there is no difference of each arguments. + * We independ first argument just served for expansion. + */ + template + requires(ALL_SAME_ENUM) + constexpr TEnum merge(TEnum val, Ts... val_left) { + using ut = std::underlying_type_t; + ut result = static_cast(val); + if constexpr (sizeof...(val_left) > 0) { + result |= static_cast(merge(val_left...)); + } + return static_cast(result); + } + + /** + * @brief Reverse given enum flags like performing ~(e) + * @tparam TEnum Enum type for processing. + * @param[in] e The list of enum flags to be inversed. + * @return The inversed enum flag. + */ + template + requires(std::is_enum_v) + constexpr TEnum invert(TEnum e) { + using ut = std::underlying_type_t; + return static_cast(~(static_cast(e))); + } + + /** + * @brief Use specified enum flag to mask given enum flag like performing e1 &= e2 + * @tparam TEnum Enum type for processing. + * @param[in,out] e1 The enum flags to be masked. + * @param[in] e2 The mask enum flag. + */ + template + requires(std::is_enum_v) + constexpr void mask(TEnum& e1, TEnum e2) { + using ut = std::underlying_type_t; + e1 = static_cast(static_cast(e1) & static_cast(e2)); + } + + /** + * @brief Add multiple enum flags to given enum flag like performing e1 |= (e2 | e3 | ... | en) + * @tparam TEnum Enum type for processing. + * @param[in,out] e1 The enum flag which flags add on. + * @param[in] vals The enum flag to be added. + */ + template + requires(ALL_SAME_ENUM) + constexpr void add(TEnum& e1, Ts... vals) { + using ut = std::underlying_type_t; + e1 = static_cast(static_cast(e1) | static_cast(merge(vals...))); + } + + /** + * @brief Remove multiple enum flags from given enum flag like performing e1 &= ~(e2 | e3 | ... | en) + * @tparam TEnum Enum type for processing. + * @param[in,out] e1 The enum flag which flags removed from. + * @param[in] vals The enum flag to be removed. + */ + template + requires(ALL_SAME_ENUM) + constexpr void remove(TEnum& e1, Ts... vals) { + using ut = std::underlying_type_t; + e1 = static_cast(static_cast(e1) & static_cast(invert(merge(vals...)))); + } + + /** + * @brief Check whether given enum flag has any of specified multiple enum flags (OR) like performing bool(e1 & (e2 | e3 | ... | en)) + * @tparam TEnum Enum type for processing. + * @param[in] e1 The enum flag where we check. + * @param[in] vals The enum flags for checking. + * @return True if it has any of given flags (OR), otherwise false. + */ + template + requires(ALL_SAME_ENUM) + constexpr bool has(TEnum e1, Ts... vals) { + using ut = std::underlying_type_t; + return static_cast(static_cast(e1) & static_cast(merge(vals...))); + } + + /** + * @brief Cast given enum flags to its equvalent boolean value like performing bool(e) + * @tparam TEnum Enum type for processing. + * @param e The enum flags to be cast. + * @return The equvalent bool value of given enum flag. + */ + template + requires(ALL_SAME_ENUM) + constexpr bool boolean(TEnum e) { + using ut = std::underlying_type_t; + return static_cast(static_cast(e)); + } + +} // namespace yycc::flag_enum diff --git a/testbench/CMakeLists.txt b/testbench/CMakeLists.txt index 269ab3d..50966c5 100644 --- a/testbench/CMakeLists.txt +++ b/testbench/CMakeLists.txt @@ -5,6 +5,7 @@ target_sources(YYCCTestbench PRIVATE main.cpp yycc/macro/version_cmp.cpp + yycc/flag_enum.cpp yycc/constraint.cpp yycc/constraint/builder.cpp yycc/string/op.cpp diff --git a/testbench/yycc/flag_enum.cpp b/testbench/yycc/flag_enum.cpp new file mode 100644 index 0000000..636c3ab --- /dev/null +++ b/testbench/yycc/flag_enum.cpp @@ -0,0 +1,77 @@ +#include +#include +#include + +#include + +#define FLAG_ENUM ::yycc::flag_enum + +namespace yycctest::flag_enum { + + enum class TestEnum : u8 { + Bit1 = 0b00000001, + Bit2 = 0b00000010, + Bit3 = 0b00000100, + Bit4 = 0b00001000, + Bit5 = 0b00010000, + Bit6 = 0b00100000, + Bit7 = 0b01000000, + Bit8 = 0b10000000, + Empty = 0b00000000, + InvBit8 = 0b01111111, + MergedBit247 = Bit2 + Bit4 + Bit7, + }; + + TEST(FlagEnum, Merge) { + EXPECT_EQ(FLAG_ENUM::merge(TestEnum::Bit2, TestEnum::Bit4, TestEnum::Bit7), TestEnum::MergedBit247); + } + + TEST(FlagEnum, Invert) { + EXPECT_EQ(FLAG_ENUM::invert(TestEnum::Bit8), TestEnum::InvBit8); + } + + TEST(FlagEnum, Mask) { + TestEnum src = FLAG_ENUM::merge(TestEnum::Bit2, TestEnum::Bit4); + TestEnum val; + + val = src; + FLAG_ENUM::mask(val, TestEnum::Bit2); + EXPECT_EQ(val, TestEnum::Bit2); + + val = src; + FLAG_ENUM::mask(val, TestEnum::Bit4); + EXPECT_EQ(val, TestEnum::Bit4); + + val = src; + FLAG_ENUM::mask(val, TestEnum::Bit3); + EXPECT_EQ(val, TestEnum::Empty); + } + + TEST(FlagEnum, Add) { + TestEnum val = TestEnum::Bit2; + FLAG_ENUM::add(val, TestEnum::Bit4, TestEnum::Bit7); + EXPECT_EQ(val, TestEnum::MergedBit247); + } + + TEST(FlagEnum, Remove) { + TestEnum val = TestEnum::MergedBit247; + FLAG_ENUM::remove(val, TestEnum::Bit2, TestEnum::Bit7); + EXPECT_EQ(val, TestEnum::Bit4); + } + + TEST(FlagEnum, Has) { + TestEnum val = TestEnum::MergedBit247; + EXPECT_TRUE(FLAG_ENUM::has(val, TestEnum::Bit2)); + EXPECT_FALSE(FLAG_ENUM::has(val, TestEnum::Bit3)); + EXPECT_TRUE(FLAG_ENUM::has(val, TestEnum::Bit4)); + EXPECT_TRUE(FLAG_ENUM::has(val, TestEnum::Bit7)); + } + + TEST(FlagEnum, Boolean) { + EXPECT_FALSE(FLAG_ENUM::boolean(TestEnum::Empty)); + EXPECT_TRUE(FLAG_ENUM::boolean(TestEnum::Bit1)); + EXPECT_TRUE(FLAG_ENUM::boolean(TestEnum::InvBit8)); + EXPECT_TRUE(FLAG_ENUM::boolean(TestEnum::MergedBit247)); + } + +}