Skip to content

Commit

Permalink
introduce bitmask support and std::format specialization
Browse files Browse the repository at this point in the history
  • Loading branch information
mguludag committed Jun 7, 2024
1 parent 0e5b19f commit d688ddd
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 39 deletions.
11 changes: 6 additions & 5 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"files.associations": {
"cmath": "cpp",
"string_view": "cpp"
}
}
"files.associations": {
"cmath": "cpp",
"string_view": "cpp",
"sstream": "cpp"
}
}
52 changes: 35 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,44 @@ 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 <sub>(default range: `[0, 256)`)</sub> on each call or with your special function for types or adding specialized `enum_range<Enum>` 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<Enum, EnumValue>() noexcept` function
* Supports bitmasked enums and auto detect them
* Supports iterate over enum (names and values) with `mgutility::enum_for_each<T>()` class and it is compatible with standard ranges and views

## Limitations
* Compiler versions
* 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 <iostream>
#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<rgb_color>(mgutility::enum_to_underlying(lhs) |
mgutility::enum_to_underlying(rhs));
}

auto operator&(rgb_color lhs, rgb_color rhs) -> rgb_color {
return static_cast<rgb_color>(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<rgb_color, rgb_color::unknown>() noexcept
constexpr inline auto
mgutility::detail::enum_type::name<rgb_color, rgb_color::unknown>() noexcept
-> string_view {
return "UNKNOWN";
}
Expand All @@ -38,38 +55,41 @@ constexpr auto mgutility::detail::enum_type::name<rgb_color, rgb_color::unknown>
template <>
struct mgutility::enum_range<rgb_color> {
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 <print>
#include <ranges>
#endif


int main() {
auto x = rgb_color::blue;
auto y = mgutility::to_enum<rgb_color>("greenn");
auto y = mgutility::to_enum<rgb_color>("green|red");

#if defined(__cpp_lib_print)

// enum_for_each<T> can usable with views and range algorithms
auto colors = mgutility::enum_for_each<rgb_color>() |
std::ranges::views::filter(
[](auto &&pair) { return pair.second != "UNKNOWN"; });
// enum_for_each<T> can usable with views and range algorithms
auto colors = mgutility::enum_for_each<rgb_color>() |
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<rgb_color>()) {
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, string_view> {enum_type, name_of_enum}
}
Expand All @@ -78,8 +98,6 @@ auto colors = mgutility::enum_for_each<rgb_color>() |
// default signature: enum_name<min_value = -128, max_value = 128, Enum
// typename>(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
Expand Down
166 changes: 149 additions & 17 deletions include/enum_name.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#if defined(_MSC_VER) && _MSC_VER < 1910
#error "Requires MSVC 2017 or newer!"
Expand All @@ -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 <optional>
#endif
Expand All @@ -72,6 +83,13 @@ struct is_scoped_enum {
std::is_enum<E>::value &&
!std::is_convertible<E, typename std::underlying_type<E>::type>::value;
};

template <typename T, typename = void>
struct has_bit_or : std::false_type {};

template <typename T>
struct has_bit_or<T, decltype((T{} | T{}), void())> : std::true_type {};

#if MG_ENUM_NAME_CPLUSPLUS > 201103L
template <typename E>
static constexpr bool is_scoped_enum_v = is_scoped_enum<E>::value;
Expand Down Expand Up @@ -131,8 +149,14 @@ class basic_string_view {
size_t len) const noexcept {
return basic_string_view<Char>(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==(
Expand Down Expand Up @@ -168,6 +192,8 @@ class basic_string_view {
return os;
}

static constexpr auto npos = -1;

private:
size_t size_;
const Char* data_;
Expand Down Expand Up @@ -297,6 +323,11 @@ using enable_if_t = std::enable_if_t<B, T>;

#endif

template <typename T>
using string_or_view_t =
typename std::conditional<has_bit_or<T>::value, std::string,
detail::string_view>::type;

template <typename Enum, Enum...>
struct enum_sequence {};

Expand Down Expand Up @@ -362,20 +393,21 @@ struct enum_type {
};

template <typename Enum>
using enum_pair = std::pair<Enum, detail::string_view>;
using enum_pair = std::pair<Enum, detail::string_or_view_t<Enum>>;

template <typename Enum, Enum... Is>
inline auto get_enum_array(detail::enum_sequence<Enum, Is...>) noexcept
MG_ENUM_NAME_CNSTXPR inline auto get_enum_array(
detail::enum_sequence<Enum, Is...>) noexcept
-> std::array<detail::string_view, sizeof...(Is) + 1> {
static std::array<detail::string_view, sizeof...(Is) + 1>
arr{"", enum_type::template name<Enum, Is>()...};
return arr;
return std::array<detail::string_view, sizeof...(Is) + 1>{
"", enum_type::template name<Enum, Is>()...};
}

template <typename Enum, int Min, int Max>
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<Enum> {
auto arr = get_enum_array<Enum>(detail::make_enum_sequence<Enum, Min, Max>());
MG_ENUM_NAME_CNSTXPR auto arr =
get_enum_array<Enum>(detail::make_enum_sequence<Enum, Min, Max>());
const auto index{std::find(arr.begin() + 1, arr.end(), str)};
return index == arr.end()
? detail::nullopt
Expand All @@ -384,11 +416,85 @@ inline auto to_enum_impl(detail::string_view str) noexcept
}

template <typename Enum, int Min, int Max>
inline auto enum_name_impl(Enum e) noexcept -> detail::string_view {
auto arr = get_enum_array<Enum>(detail::make_enum_sequence<Enum, Min, Max>());
const auto index{std::abs(Min) + static_cast<int>(e) + (Min < 0 ? 1 : 1)};
MG_ENUM_NAME_CNSTXPR inline auto to_enum_bitmask_impl(
detail::string_view str) noexcept -> detail::optional<Enum> {
if (str.find('|') == detail::string_view::npos) {
return to_enum_impl<Enum, Min, Max>(str);
} else {
auto index = std::size_t{};
auto result = detail::optional<Enum>{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<Enum, Min, Max>(str.substr(index, i - index));
if (!name.empty() && maybe_enum) {
result.emplace(
result ? static_cast<Enum>(*result | *maybe_enum)
: *maybe_enum);
}

index = i + 1U;
}
}
if (result) {
auto maybe_enum = to_enum_impl<Enum, Min, Max>(
str.substr(index, str.size() - index));
if (maybe_enum) {
result.emplace(static_cast<Enum>(*result | *maybe_enum));
} else {
result.reset();
}
}

return result;
}
}

template <typename Enum, int Min, int Max,
detail::enable_if_t<!detail::has_bit_or<Enum>::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<Enum>(detail::make_enum_sequence<Enum, Min, Max>());
const auto index{(Min < 0 ? Min * -1 : Min) + static_cast<int>(e) +
(Min < 0 ? 1 : 1)};
return arr[(index < Min || index > arr.size() - 1) ? 0 : index];
}

template <typename Enum, int Min, int Max,
detail::enable_if_t<detail::has_bit_or<Enum>::value, bool> = true>
MG_ENUM_NAME_CNSTXPR_FUNC inline auto enum_name_impl(Enum e) noexcept
-> detail::string_or_view_t<Enum> {
MG_ENUM_NAME_CNSTXPR auto arr =
get_enum_array<Enum>(detail::make_enum_sequence<Enum, Min, Max>());
const auto index{(Min < 0 ? Min * -1 : Min) + static_cast<int>(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<int>(c) < 58 && static_cast<int>(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<Enum>(i)) == static_cast<Enum>(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

Expand Down Expand Up @@ -462,7 +568,7 @@ constexpr inline auto enum_to_underlying(Enum e) noexcept

template <int Min, int Max, typename Enum>
MG_ENUM_NAME_CNSTXPR inline auto enum_name(Enum e) noexcept
-> detail::string_view {
-> detail::string_or_view_t<Enum> {
static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!");
static_assert(std::is_enum<Enum>::value, "Value is not an Enum type!");
return detail::enum_name_impl<Enum, Min, Max>(e);
Expand All @@ -471,7 +577,7 @@ MG_ENUM_NAME_CNSTXPR inline auto enum_name(Enum e) noexcept
template <typename Enum, int Min = enum_range<Enum>::min,
int Max = enum_range<Enum>::max>
MG_ENUM_NAME_CNSTXPR inline auto enum_name(Enum e) noexcept
-> detail::string_view {
-> detail::string_or_view_t<Enum> {
static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!");
static_assert(std::is_enum<Enum>::value, "Value is not an Enum type!");
return detail::enum_name_impl<Enum, Min, Max>(e);
Expand All @@ -484,13 +590,24 @@ auto enum_for_each<Enum>::enum_iter::operator*() const -> value_type {
}

template <typename Enum, int Min = enum_range<Enum>::min,
int Max = enum_range<Enum>::max>
int Max = enum_range<Enum>::max,
detail::enable_if_t<!detail::has_bit_or<Enum>::value, bool> = true>
MG_ENUM_NAME_CNSTXPR inline auto to_enum(detail::string_view str) noexcept
-> detail::optional<Enum> {
static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!");
static_assert(std::is_enum<Enum>::value, "Type is not an Enum type!");
return detail::to_enum_impl<Enum, Min, Max>(str);
}

template <typename Enum, int Min = enum_range<Enum>::min,
int Max = enum_range<Enum>::max,
detail::enable_if_t<detail::has_bit_or<Enum>::value, bool> = true>
MG_ENUM_NAME_CNSTXPR inline auto to_enum(detail::string_view str) noexcept
-> detail::optional<Enum> {
static_assert(Min < Max - 1, "Max must be greater than (Min + 1)!");
static_assert(std::is_enum<Enum>::value, "Type is not an Enum type!");
return detail::to_enum_bitmask_impl<Enum, Min, Max>(str);
}
} // namespace mgutility

template <typename Enum,
Expand All @@ -501,4 +618,19 @@ auto operator<<(std::ostream& os, Enum e) -> std::ostream& {
return os;
}

#endif // MGUTILITY_ENUM_NAME_HPP
#if defined(__cpp_lib_format)

#include <format>

template <typename Enum>
requires std::is_enum_v<Enum>
struct std::formatter<Enum> : formatter<std::string_view> {
auto constexpr format(Enum e, format_context& ctx) const {
return formatter<std::string_view>::format(mgutility::enum_name(e),
ctx);
}
};

#endif

#endif // MGUTILITY_ENUM_NAME_HPP

0 comments on commit d688ddd

Please sign in to comment.