feat: add new helper for scoped enum type.

- Add EnumHelper for bitwise operation of scoped enum type (copied from libcmo21)
- Enrich the return value of ConfigManager load function to present more infomation of loading.
- update testbench for new added feature and modification.
- add document for new added feature.
This commit is contained in:
yyc12345 2024-11-02 17:10:55 +08:00
parent 50dd086b53
commit 21f7e7f786
8 changed files with 327 additions and 24 deletions

35
doc/src/enum_helper.dox Normal file
View File

@ -0,0 +1,35 @@
namespace YYCC::EnumHelper {
/**
\page enum_helper Scoped Enum Helper
\section enum_helper__intro Intro
C++ introduce a new enum called scoped enum.
It is better than legacy C enum because it will not leak name into namespace where it locate,
and also can specify an underlying type to it to make sure it is stored as specified size.
However, the shortcoming of it is that it lack bitwise operator comparing with legacy C enum.
Programmer must implement them for scoped enum one by one.
It is a hardship and inconvenient.
This is the reason why I invent this class
\section enum_helper__Usage Usage
In this namespace, we provide all bitwise functions related to scoped enum type which may be used.
See YYCC::EnumHelper for more detail (It is more clear to read function annotation than I introduce in there repeatedly).
\section enum_helper__why Why not Operator Overload
I have try it (and you even can see the relic of it in source code).
But it need a extra statement written in following to include it, otherwise compiler can not see it.
\code
using namespace YYCC::EnumHelper;
\endcode
Another reason why I do not use this method is that
this overload strategy may be applied to some type which should not be applied by accient, such as non-scoped enum type.
So I gave up this solution.
*/
}

View File

@ -45,6 +45,8 @@
\li \subpage std_patch \li \subpage std_patch
\li \subpage enum_helper
<B>Advanced Features</B> <B>Advanced Features</B>
\li \subpage constraints \li \subpage constraints

View File

@ -31,6 +31,7 @@ FILES
ConsoleHelper.hpp ConsoleHelper.hpp
DialogHelper.hpp DialogHelper.hpp
EncodingHelper.hpp EncodingHelper.hpp
EnumHelper.hpp
ExceptionHelper.hpp ExceptionHelper.hpp
StdPatch.hpp StdPatch.hpp
IOHelper.hpp IOHelper.hpp

View File

@ -2,6 +2,7 @@
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include "IOHelper.hpp" #include "IOHelper.hpp"
#include "EnumHelper.hpp"
#include <stdexcept> #include <stdexcept>
namespace YYCC::ConfigManager { namespace YYCC::ConfigManager {
@ -33,7 +34,7 @@ namespace YYCC::ConfigManager {
m_CfgFilePath(cfg_file_path), m_VersionIdentifier(version_identifier), m_Settings() { m_CfgFilePath(cfg_file_path), m_VersionIdentifier(version_identifier), m_Settings() {
// Mark: no need to check cfg file path // Mark: no need to check cfg file path
// it will be checked at creating file handle // it will be checked at creating file handle
// assign settings // assign settings
for (auto* setting : settings) { for (auto* setting : settings) {
auto result = m_Settings.try_emplace(setting->GetName(), setting); auto result = m_Settings.try_emplace(setting->GetName(), setting);
@ -44,7 +45,10 @@ namespace YYCC::ConfigManager {
} }
} }
bool CoreManager::Load() { ConfigLoadResult CoreManager::Load() {
// prepare result variables
ConfigLoadResult ret = ConfigLoadResult::OK;
// reset all settings first // reset all settings first
Reset(); Reset();
@ -53,20 +57,27 @@ namespace YYCC::ConfigManager {
if (fs.get() == nullptr) { if (fs.get() == nullptr) {
// if we fail to get, it means that we do not have corresponding cfg file. // if we fail to get, it means that we do not have corresponding cfg file.
// all settings should be reset to default value. // all settings should be reset to default value.
return true; ret = ConfigLoadResult::Created;
return ret;
} }
// fetch version info // fetch version info
uint64_t version_info; uint64_t version_info;
if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info)) if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info)) {
return false; ret = ConfigLoadResult::Created;
return ret;
}
// check version // check version
// if read version is greater than we expected, // if read version is greater than we expected,
// it means that this cfg file is created by the program higer than this. // it means that this cfg file is created by the program higer than this.
// we should not read anything from it. // we should not read anything from it.
// however, for compaitibility reason, we allow read old cfg data. // however, for compaitibility reason, we allow read old cfg data.
if (version_info > m_VersionIdentifier) if (version_info > m_VersionIdentifier) {
return true; ret = ConfigLoadResult::ForwardNew;
return ret;
} else if (version_info < m_VersionIdentifier) {
EnumHelper::Add(ret, ConfigLoadResult::Migrated);
}
// fetch setting item from file // fetch setting item from file
yycc_u8string name_cache; yycc_u8string name_cache;
@ -77,37 +88,50 @@ namespace YYCC::ConfigManager {
if (std::fread(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length)) { if (std::fread(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length)) {
// we also check whether reach EOF at there. // we also check whether reach EOF at there.
if (std::feof(fs.get())) break; if (std::feof(fs.get())) break;
else return false; else {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
} }
// fetch name body // fetch name body
name_cache.resize(name_length); name_cache.resize(name_length);
if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length) if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length) {
return false; EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// get setting data length // get setting data length
size_t data_length; size_t data_length;
if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length)) if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length)) {
return false; EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// get matched setting first // get matched setting first
const auto& found = m_Settings.find(name_cache); const auto& found = m_Settings.find(name_cache);
if (found != m_Settings.end()) { if (found != m_Settings.end()) {
// found. read data for it // found. read data for it
found->second->ResizeData(data_length); found->second->ResizeData(data_length);
if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length) if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length) {
return false; EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// call user defined load function // call user defined load function
// if fail to parse, reset to default value // if fail to parse, reset to default value
if (!found->second->UserLoad()) if (!found->second->UserLoad()) {
EnumHelper::Add(ret, ConfigLoadResult::ItemError);
found->second->UserReset(); found->second->UserReset();
}
} else { } else {
// fail to find. skip this unknown setting // fail to find. skip this unknown setting
if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0) if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0) {
return false; EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
} }
} }
return true; return ret;
} }
bool CoreManager::Save() { bool CoreManager::Save() {

View File

@ -17,6 +17,19 @@
* @details For how to use this namespace, please see \ref config_manager. * @details For how to use this namespace, please see \ref config_manager.
*/ */
namespace YYCC::ConfigManager { namespace YYCC::ConfigManager {
/**
* @brief The load result of loading config.
*/
enum class ConfigLoadResult {
OK = 0, ///< Success load configs.
Created = 1 << 0, ///< Given file is not existing, we create all configs in default values.
ForwardNew = 1 << 1, ///< Detect the config file created by higher version. We create all configs in default values.
Migrated = 1 << 2, ///< Detect the config file created by lower version. We try migrate configs written in it.
BrokenFile = 1 << 3, ///< Given file has bad format. Thus some configs are kept as its default values.
ItemError = 1 << 4 ///< Some config can not be recognized from the data read from file so they are reset to default value.
};
using UnderlyingConfigLoadResult_t = std::underlying_type_t<ConfigLoadResult>;
/// @brief The base class of every setting. /// @brief The base class of every setting.
/// @details Programmer can inherit this class and implement essential functions to create custom setting. /// @details Programmer can inherit this class and implement essential functions to create custom setting.
@ -75,7 +88,7 @@ namespace YYCC::ConfigManager {
private: private:
std::vector<uint8_t> m_RawData; std::vector<uint8_t> m_RawData;
}; };
/// @brief Settings manager and config file reader writer. /// @brief Settings manager and config file reader writer.
class CoreManager { class CoreManager {
public: public:
@ -96,8 +109,8 @@ namespace YYCC::ConfigManager {
public: public:
/// @brief Load settings from file. /// @brief Load settings from file.
/// @details Before loading, all settings will be reset to default value first. /// @details Before loading, all settings will be reset to default value first.
/// @return True if success, otherwise false. /// @return What happend when loading config. This function always success.
bool Load(); ConfigLoadResult Load();
/// @brief Save settings to file. /// @brief Save settings to file.
/// @return True if success, otherwise false. /// @return True if success, otherwise false.
bool Save(); bool Save();

177
src/EnumHelper.hpp Normal file
View File

@ -0,0 +1,177 @@
#pragma once
#include "YYCCInternal.hpp"
#include <initializer_list>
#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::EnumHelper {
//// Reference:
//// Enum operator overload: https://stackoverflow.com/a/71107019
//// Constexpr operator overload: https://stackoverflow.com/a/17746099
//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));
//}
/**
* @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 all_enum_values {
public:
// Please note it is 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>>...>;
};
/**
* @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_enum_values_v = all_enum_values<TEnum, Ts...>::value;
/**
* @brief Merge given enum flags like performing <TT>e1 | e2 | ... | en</TT>
* @tparam TEnum Enum type for processing.
* @param[in] il The list of enum flags to be merged.
* @return The merged enum flag.
*/
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum, Ts...>, int> = 0>
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, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr TEnum Invert(TEnum e) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<TEnum>(~(static_cast<ut>(e)));
}
/**
* @brief Use specified enum flags to mask given enum flags 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, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
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 specified enum flags to given enum flags like performing <TT>e1 = e1 | e2 | ... | en</TT>
* @tparam TEnum Enum type for processing.
* @param[in,out] e1 The enum flags to be processed.
* @param[in] e2 The enum flag to be added.
*/
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum, Ts...>, int> = 0>
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 specified enum flags from given enum flags like performing <TT>e1 &= ~(e2 | e3 | ... | en)</TT>
* @tparam TEnum Enum type for processing.
* @param[in,out] e1 The enum flags to be processed.
* @param[in] e2 The enum flag to be removed.
*/
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum>, int> = 0>
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 flags has specified enum flag like performing <TT>bool(e & probe)</TT>
* @tparam TEnum Enum type for processing.
* @param[in] e1 The enum flags to be checked.
* @param[in] e2 The enum flag for checking.
* @return True if it has, otherwise false.
*/
template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr bool Has(TEnum e1, TEnum e2) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<bool>(static_cast<ut>(e1) & static_cast<ut>(e2));
}
/**
* @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 cast enum flag.
*/
template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr bool Bool(TEnum e) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<bool>(static_cast<ut>(e));
}
}

View File

@ -11,6 +11,7 @@
#include "IOHelper.hpp" #include "IOHelper.hpp"
#include "WinFctHelper.hpp" #include "WinFctHelper.hpp"
#include "StdPatch.hpp" #include "StdPatch.hpp"
#include "EnumHelper.hpp"
#include "ExceptionHelper.hpp" #include "ExceptionHelper.hpp"
#include "ConfigManager.hpp" #include "ConfigManager.hpp"

View File

@ -402,7 +402,7 @@ namespace YYCCTestbench {
#endif #endif
} }
static void StdPatch() { static void StdPatchTestbench() {
// Std Path // Std Path
@ -439,6 +439,51 @@ namespace YYCCTestbench {
} }
enum class TestFlagEnum : uint8_t {
Test1 = 0b00000000,
Test2 = 0b00000001,
Test3 = 0b00000010,
Test4 = 0b00000100,
Test5 = 0b00001000,
Test6 = 0b00010000,
Test7 = 0b00100000,
Test8 = 0b01000000,
Test9 = 0b10000000,
Inverted = 0b01111111,
Merged = Test3 + Test5,
};
static void EnumHelperTestbench() {
TestFlagEnum val;
Assert(YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5) == TestFlagEnum::Merged, YYCC_U8("YYCC::EnumHelper::Merge"));
Assert(YYCC::EnumHelper::Invert(TestFlagEnum::Test9) == TestFlagEnum::Inverted, YYCC_U8("YYCC::EnumHelper::Invert"));
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
YYCC::EnumHelper::Mask(val, TestFlagEnum::Test3);
Assert(YYCC::EnumHelper::Bool(val), YYCC_U8("YYCC::EnumHelper::Mask"));
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
YYCC::EnumHelper::Mask(val, TestFlagEnum::Test4);
Assert(!YYCC::EnumHelper::Bool(val), YYCC_U8("YYCC::EnumHelper::Mask"));
val = TestFlagEnum::Test3;
YYCC::EnumHelper::Add(val, TestFlagEnum::Test5);
Assert(val == TestFlagEnum::Merged, YYCC_U8("YYCC::EnumHelper::Add"));
val = TestFlagEnum::Merged;
YYCC::EnumHelper::Remove(val, TestFlagEnum::Test5);
Assert(val == TestFlagEnum::Test3, YYCC_U8("YYCC::EnumHelper::Remove"));
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
Assert(YYCC::EnumHelper::Has(val, TestFlagEnum::Test3), YYCC_U8("YYCC::EnumHelper::Has"));
Assert(!YYCC::EnumHelper::Has(val, TestFlagEnum::Test4), YYCC_U8("YYCC::EnumHelper::Has"));
Assert(!YYCC::EnumHelper::Bool(TestFlagEnum::Test1), YYCC_U8("YYCC::EnumHelper::Bool"));
Assert(YYCC::EnumHelper::Bool(TestFlagEnum::Test2), YYCC_U8("YYCC::EnumHelper::Bool"));
}
enum class TestEnum : int8_t { enum class TestEnum : int8_t {
Test1, Test2, Test3 Test1, Test2, Test3
}; };
@ -518,7 +563,11 @@ namespace YYCCTestbench {
Assert(test.m_EnumSetting.Get() == TestEnum::Test1, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset")); Assert(test.m_EnumSetting.Get() == TestEnum::Test1, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
// test load // test load
Assert(test.m_CoreManager.Load(), YYCC_U8("YYCC::ConfigManager::CoreManager::Load")); YYCC::ConfigManager::ConfigLoadResult wrong_result = YYCC::EnumHelper::Merge(
YYCC::ConfigManager::ConfigLoadResult::ItemError,
YYCC::ConfigManager::ConfigLoadResult::BrokenFile
);
Assert(!YYCC::EnumHelper::Has(test.m_CoreManager.Load(), wrong_result), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
test.PrintSettings(); test.PrintSettings();
Assert(test.m_IntSetting.Get() == INT32_C(114), YYCC_U8("YYCC::ConfigManager::CoreManager::Load")); Assert(test.m_IntSetting.Get() == INT32_C(114), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_FloatSetting.Get() == 2.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load")); Assert(test.m_FloatSetting.Get() == 2.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
@ -655,7 +704,8 @@ int main(int argc, char* argv[]) {
YYCCTestbench::StringTestbench(); YYCCTestbench::StringTestbench();
YYCCTestbench::ParserTestbench(); YYCCTestbench::ParserTestbench();
YYCCTestbench::WinFctTestbench(); YYCCTestbench::WinFctTestbench();
YYCCTestbench::StdPatch(); YYCCTestbench::StdPatchTestbench();
YYCCTestbench::EnumHelperTestbench();
// advanced // advanced
YYCCTestbench::ConfigManagerTestbench(); YYCCTestbench::ConfigManagerTestbench();
YYCCTestbench::ArgParserTestbench(argc, argv); YYCCTestbench::ArgParserTestbench(argc, argv);