refactor: refactor enum helper as flag enum.

- refactor enum helper.
- add testbench for it.
This commit is contained in:
2025-08-04 22:31:37 +08:00
parent 54134b342e
commit b9f81c16a0
4 changed files with 274 additions and 0 deletions

View File

@ -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

195
src/yycc/flag_enum.hpp Normal file
View File

@ -0,0 +1,195 @@
#pragma once
#include <type_traits>
/**
* @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<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator|(TEnum lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(static_cast<ut>(lhs) | static_cast<ut>(rhs));
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator|=(TEnum& lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// lhs = lhs | rhs;
// return lhs;
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator&(TEnum lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(static_cast<ut>(lhs) & static_cast<ut>(rhs));
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator&=(TEnum& lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// lhs = lhs & rhs;
// return lhs;
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator^(TEnum lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(static_cast<ut>(lhs) ^ static_cast<ut>(rhs));
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator^=(TEnum& lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// lhs = lhs ^ rhs;
// return lhs;
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator~(TEnum lhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(~(static_cast<ut>(lhs)));
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr bool operator bool(TEnum lhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<bool>(static_cast<ut>(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<typename TEnum, typename... Ts>
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::remove_cv_t<TEnum>>
&& std::conjunction_v<std::is_same<std::remove_cv_t<TEnum>, std::remove_cv_t<Ts>>...>;
};
/**
* @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<typename TEnum, typename... Ts>
inline constexpr bool ALL_SAME_ENUM = AllSameEnum<TEnum, Ts...>::value;
/**
* @brief Merge given enum flags like performing <TT>e1 | e2 | ... | en</TT>
* @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<typename TEnum, typename... Ts>
requires(ALL_SAME_ENUM<TEnum, Ts...>)
constexpr TEnum merge(TEnum val, Ts... val_left) {
using ut = std::underlying_type_t<TEnum>;
ut result = static_cast<ut>(val);
if constexpr (sizeof...(val_left) > 0) {
result |= static_cast<ut>(merge(val_left...));
}
return static_cast<TEnum>(result);
}
/**
* @brief Reverse given enum flags like performing <TT>~(e)</TT>
* @tparam TEnum Enum type for processing.
* @param[in] e The list of enum flags to be inversed.
* @return The inversed enum flag.
*/
template<typename TEnum>
requires(std::is_enum_v<TEnum>)
constexpr TEnum invert(TEnum e) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<TEnum>(~(static_cast<ut>(e)));
}
/**
* @brief Use specified enum flag to mask given enum flag like performing <TT>e1 &= e2</TT>
* @tparam TEnum Enum type for processing.
* @param[in,out] e1 The enum flags to be masked.
* @param[in] e2 The mask enum flag.
*/
template<typename TEnum>
requires(std::is_enum_v<TEnum>)
constexpr void mask(TEnum& e1, TEnum e2) {
using ut = std::underlying_type_t<TEnum>;
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(e2));
}
/**
* @brief Add multiple enum flags to given enum flag like performing <TT>e1 |= (e2 | e3 | ... | en)</TT>
* @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<typename TEnum, typename... Ts>
requires(ALL_SAME_ENUM<TEnum, Ts...>)
constexpr void add(TEnum& e1, Ts... vals) {
using ut = std::underlying_type_t<TEnum>;
e1 = static_cast<TEnum>(static_cast<ut>(e1) | static_cast<ut>(merge(vals...)));
}
/**
* @brief Remove multiple enum flags from given enum flag like performing <TT>e1 &= ~(e2 | e3 | ... | en)</TT>
* @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<typename TEnum, typename... Ts>
requires(ALL_SAME_ENUM<TEnum, Ts...>)
constexpr void remove(TEnum& e1, Ts... vals) {
using ut = std::underlying_type_t<TEnum>;
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(invert(merge(vals...))));
}
/**
* @brief Check whether given enum flag has any of specified multiple enum flags (OR) like performing <TT>bool(e1 & (e2 | e3 | ... | en))</TT>
* @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<typename TEnum, typename... Ts>
requires(ALL_SAME_ENUM<TEnum, Ts...>)
constexpr bool has(TEnum e1, Ts... vals) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<bool>(static_cast<ut>(e1) & static_cast<ut>(merge(vals...)));
}
/**
* @brief Cast given enum flags to its equvalent boolean value like performing <TT>bool(e)</TT>
* @tparam TEnum Enum type for processing.
* @param e The enum flags to be cast.
* @return The equvalent bool value of given enum flag.
*/
template<typename TEnum, typename... Ts>
requires(ALL_SAME_ENUM<TEnum, Ts...>)
constexpr bool boolean(TEnum e) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<bool>(static_cast<ut>(e));
}
} // namespace yycc::flag_enum

View File

@ -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

View File

@ -0,0 +1,77 @@
#include <gtest/gtest.h>
#include <yycc.hpp>
#include <yycc/flag_enum.hpp>
#include <yycc/rust/prelude.hpp>
#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));
}
}