diff --git a/.vscode/settings.json b/.vscode/settings.json index abcce64..d1e80c1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { - "files.associations": { - "cmath": "cpp", - "string_view": "cpp" - } -} \ No newline at end of file + "files.associations": { + "cmath": "cpp", + "string_view": "cpp", + "sstream": "cpp" + } +} diff --git a/README.md b/README.md index 523b10c..ac10478 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ Converting (scoped)enum values to/from string names written in C++>=11. ## Features * Supports `enum` and `enum class` * Supports enums in namespaces, classes or structs even templated or not -* Supports compile-time as much as possible using with C++14 and later +* Supports compile-time as much as possible using with C++17 and later * Changing enum range with template parameter (default range: `[0, 256)`) on each call or with your special function for types or adding specialized `enum_range` struct * Supports and automatically overloaded `operator<<` for Enum types to direct using with ostream objects * Supports custom enum name output by explicit specialization of `constexpr inline auto mgutility::detail::enum_type::name() noexcept` function +* Supports bitmasked enums and auto detect them * Supports iterate over enum (names and values) with `mgutility::enum_for_each()` class and it is compatible with standard ranges and views ## Limitations @@ -20,16 +21,32 @@ Converting (scoped)enum values to/from string names written in C++>=11. * Wider range can increase compile time so user responsible to adjusting for enum's range -## Usage ([try it!](https://godbolt.org/z/YM5EvY1Y5)) +## Usage ([try it!](https://godbolt.org/z/dfeYxscvT)) ```C++ #include #include "enum_name.hpp" -num class rgb_color { red, green, blue, unknown = -1 }; +enum class rgb_color { + red = 1 << 0, + green = 1 << 1, + blue = 1 << 2, + unknown = -1 +}; + +auto operator|(rgb_color lhs, rgb_color rhs) -> rgb_color { + return static_cast(mgutility::enum_to_underlying(lhs) | + mgutility::enum_to_underlying(rhs)); +} + +auto operator&(rgb_color lhs, rgb_color rhs) -> rgb_color { + return static_cast(mgutility::enum_to_underlying(lhs) & + mgutility::enum_to_underlying(rhs)); +} // specialize rgb_color::unknown to make output "UNKNOWN" instead of "unknown" template <> -constexpr auto mgutility::detail::enum_type::name() noexcept +constexpr inline auto +mgutility::detail::enum_type::name() noexcept -> string_view { return "UNKNOWN"; } @@ -38,38 +55,41 @@ constexpr auto mgutility::detail::enum_type::name template <> struct mgutility::enum_range { static constexpr auto min = -1; - static constexpr auto max = 3; + static constexpr auto max = 5; }; // you can specialize enum ranges with overload per enum types (option 2) -auto enum_name = [](rgb_color c) { return mgutility::enum_name<-1, 3>(c); }; +auto enum_name = [](rgb_color c) { return mgutility::enum_name<-1, 5>(c); }; #if defined(__cpp_lib_print) #include #include #endif - int main() { auto x = rgb_color::blue; - auto y = mgutility::to_enum("greenn"); + auto y = mgutility::to_enum("green|red"); #if defined(__cpp_lib_print) -// enum_for_each can usable with views and range algorithms -auto colors = mgutility::enum_for_each() | - std::ranges::views::filter( - [](auto &&pair) { return pair.second != "UNKNOWN"; }); + // enum_for_each can usable with views and range algorithms + auto colors = mgutility::enum_for_each() | + std::ranges::views::filter([](auto &&pair) { + return !pair.second.empty() && pair.second != "UNKNOWN"; + }); std::ranges::for_each(colors, [](auto &&color) { - std::println("{} \t: {}", color.second, mgutility::enum_to_underlying(color.first)); + std::println("{} \t: {}", color.second, + mgutility::enum_to_underlying(color.first)); }); #else for (auto&& e : mgutility::enum_for_each()) { - if(e.second != "UNKNOWN"){ - std::cout << e.second << " \t: " << mgutility::enum_to_underlying(e.first) << '\n'; + if (!e.second.empty() && e.second != "UNKNOWN") { + std::cout << e.second + << " \t: " << mgutility::enum_to_underlying(e.first) + << '\n'; } // std::pair {enum_type, name_of_enum} } @@ -78,8 +98,6 @@ auto colors = mgutility::enum_for_each() | // default signature: enum_name(Enum&&) Changing max_value to not too much greater than enum's // max value, it will compiles faster - std::cout << mgutility::enum_name(x) << '\n'; // will print "blue" to output - // or std::cout << x << '\n'; // will print "blue" to output // calling specialized enum ranges function for rgb_color type diff --git a/include/enum_name.hpp b/include/enum_name.hpp index c7ed6ab..0cac014 100644 --- a/include/enum_name.hpp +++ b/include/enum_name.hpp @@ -33,7 +33,6 @@ #include #include #include -#include #if defined(_MSC_VER) && _MSC_VER < 1910 #error "Requires MSVC 2017 or newer!" @@ -59,6 +58,18 @@ #error "Standards older than C++11 is not supported!" #endif +#if MG_ENUM_NAME_CPLUSPLUS > 201703L +#define MG_ENUM_NAME_CNSTXPR_FUNC constexpr +#else +#define MG_ENUM_NAME_CNSTXPR_FUNC +#endif + +#if MG_ENUM_NAME_CPLUSPLUS > 201703L +#define MG_ENUM_NAME_CNSTEVL consteval +#else +#define MG_ENUM_NAME_CNSTEVL +#endif + #if MG_ENUM_NAME_CPLUSPLUS > 201702L #include #endif @@ -72,6 +83,13 @@ struct is_scoped_enum { std::is_enum::value && !std::is_convertible::type>::value; }; + +template +struct has_bit_or : std::false_type {}; + +template +struct has_bit_or : std::true_type {}; + #if MG_ENUM_NAME_CPLUSPLUS > 201103L template static constexpr bool is_scoped_enum_v = is_scoped_enum::value; @@ -131,8 +149,14 @@ class basic_string_view { size_t len) const noexcept { return basic_string_view(data_ + begin, len); } - constexpr inline size_t rfind(Char c, size_t pos = 0) const noexcept { - return c == data_[pos] ? pos : rfind(c, --pos); + constexpr inline size_t rfind(Char c, size_t pos = npos) const noexcept { + return (pos == npos ? pos = size_ : pos = pos), c == data_[pos] ? pos + : pos == 0U + ? npos + : rfind(c, --pos); + } + constexpr inline size_t find(Char c, size_t pos = 0) const noexcept { + return c == data_[pos] ? pos : pos < size_ ? find(c, ++pos) : npos; } constexpr friend inline bool operator==( @@ -168,6 +192,8 @@ class basic_string_view { return os; } + static constexpr auto npos = -1; + private: size_t size_; const Char* data_; @@ -297,6 +323,11 @@ using enable_if_t = std::enable_if_t; #endif +template +using string_or_view_t = + typename std::conditional::value, std::string, + detail::string_view>::type; + template struct enum_sequence {}; @@ -362,20 +393,21 @@ struct enum_type { }; template -using enum_pair = std::pair; +using enum_pair = std::pair>; template -inline auto get_enum_array(detail::enum_sequence) noexcept +MG_ENUM_NAME_CNSTXPR inline auto get_enum_array( + detail::enum_sequence) noexcept -> std::array { - static std::array - arr{"", enum_type::template name()...}; - return arr; + return std::array{ + "", enum_type::template name()...}; } template -inline auto to_enum_impl(detail::string_view str) noexcept +MG_ENUM_NAME_CNSTXPR inline auto to_enum_impl(detail::string_view str) noexcept -> detail::optional { - auto arr = get_enum_array(detail::make_enum_sequence()); + MG_ENUM_NAME_CNSTXPR auto arr = + get_enum_array(detail::make_enum_sequence()); const auto index{std::find(arr.begin() + 1, arr.end(), str)}; return index == arr.end() ? detail::nullopt @@ -384,11 +416,85 @@ inline auto to_enum_impl(detail::string_view str) noexcept } template -inline auto enum_name_impl(Enum e) noexcept -> detail::string_view { - auto arr = get_enum_array(detail::make_enum_sequence()); - const auto index{std::abs(Min) + static_cast(e) + (Min < 0 ? 1 : 1)}; +MG_ENUM_NAME_CNSTXPR inline auto to_enum_bitmask_impl( + detail::string_view str) noexcept -> detail::optional { + if (str.find('|') == detail::string_view::npos) { + return to_enum_impl(str); + } else { + auto index = std::size_t{}; + auto result = detail::optional{detail::nullopt}; + for (std::size_t i{}; i < str.size(); ++i) { + if (str[i] == '|') { + const auto name = str.substr(index, i - index); + auto maybe_enum = + to_enum_impl(str.substr(index, i - index)); + if (!name.empty() && maybe_enum) { + result.emplace( + result ? static_cast(*result | *maybe_enum) + : *maybe_enum); + } + + index = i + 1U; + } + } + if (result) { + auto maybe_enum = to_enum_impl( + str.substr(index, str.size() - index)); + if (maybe_enum) { + result.emplace(static_cast(*result | *maybe_enum)); + } else { + result.reset(); + } + } + + return result; + } +} + +template ::value, bool> = true> +MG_ENUM_NAME_CNSTXPR inline auto enum_name_impl(Enum e) noexcept + -> detail::string_view { + MG_ENUM_NAME_CNSTXPR auto arr = + get_enum_array(detail::make_enum_sequence()); + const auto index{(Min < 0 ? Min * -1 : Min) + static_cast(e) + + (Min < 0 ? 1 : 1)}; return arr[(index < Min || index > arr.size() - 1) ? 0 : index]; } + +template ::value, bool> = true> +MG_ENUM_NAME_CNSTXPR_FUNC inline auto enum_name_impl(Enum e) noexcept + -> detail::string_or_view_t { + MG_ENUM_NAME_CNSTXPR auto arr = + get_enum_array(detail::make_enum_sequence()); + const auto index{(Min < 0 ? Min * -1 : Min) + static_cast(e) + + (Min < 0 ? 1 : 1)}; + const auto name = arr[(index < Min || index > arr.size() - 1) ? 0 : index]; + + const auto isdigit = [](char c) { + return static_cast(c) < 58 && static_cast(c) > 47; + }; + + if (!name.empty() && !isdigit(name[0])) { + return std::string{name}; + } else { + auto bitmasked_name = std::string{}; + + for (auto i{Min}; i < Max; ++i) { + const auto idx = (Min < 0 ? Min * -1 : Min) + i + (Min < 0 ? 1 : 1); + if (!(idx < Min || idx > arr.size() - 1) && !arr[idx].empty() && + !isdigit(arr[idx][0]) && + (e & static_cast(i)) == static_cast(i)) { + bitmasked_name.append(arr[idx]).append("|"); + } + } + + const auto result = + bitmasked_name.substr(0U, bitmasked_name.size() - 1U); + return result.find('|') != std::string::npos ? result : std::string{""}; + } +} } // namespace detail } // namespace mgutility @@ -462,7 +568,7 @@ constexpr inline auto enum_to_underlying(Enum e) noexcept template MG_ENUM_NAME_CNSTXPR inline auto enum_name(Enum e) noexcept - -> detail::string_view { + -> detail::string_or_view_t { static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); static_assert(std::is_enum::value, "Value is not an Enum type!"); return detail::enum_name_impl(e); @@ -471,7 +577,7 @@ MG_ENUM_NAME_CNSTXPR inline auto enum_name(Enum e) noexcept template ::min, int Max = enum_range::max> MG_ENUM_NAME_CNSTXPR inline auto enum_name(Enum e) noexcept - -> detail::string_view { + -> detail::string_or_view_t { static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); static_assert(std::is_enum::value, "Value is not an Enum type!"); return detail::enum_name_impl(e); @@ -484,13 +590,24 @@ auto enum_for_each::enum_iter::operator*() const -> value_type { } template ::min, - int Max = enum_range::max> + int Max = enum_range::max, + detail::enable_if_t::value, bool> = true> MG_ENUM_NAME_CNSTXPR inline auto to_enum(detail::string_view str) noexcept -> detail::optional { static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); static_assert(std::is_enum::value, "Type is not an Enum type!"); return detail::to_enum_impl(str); } + +template ::min, + int Max = enum_range::max, + detail::enable_if_t::value, bool> = true> +MG_ENUM_NAME_CNSTXPR inline auto to_enum(detail::string_view str) noexcept + -> detail::optional { + static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!"); + static_assert(std::is_enum::value, "Type is not an Enum type!"); + return detail::to_enum_bitmask_impl(str); +} } // namespace mgutility template std::ostream& { return os; } -#endif // MGUTILITY_ENUM_NAME_HPP +#if defined(__cpp_lib_format) + +#include + +template + requires std::is_enum_v +struct std::formatter : formatter { + auto constexpr format(Enum e, format_context& ctx) const { + return formatter::format(mgutility::enum_name(e), + ctx); + } +}; + +#endif + +#endif // MGUTILITY_ENUM_NAME_HPP \ No newline at end of file