Skip to content

Commit

Permalink
introduce bitmask support
Browse files Browse the repository at this point in the history
  • Loading branch information
mguludag committed Mar 31, 2024
1 parent 0e5b19f commit 20c043a
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 28 deletions.
54 changes: 37 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/vYhYeq68c))
```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,43 @@ 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" &&
pair.second.find('|') == pair.second.npos;
});

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" &&
e.second.find('|') == e.second.npos) {
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 +100,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
110 changes: 99 additions & 11 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 Down Expand Up @@ -72,6 +71,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 +137,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 +180,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 +311,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 +381,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
-> std::array<detail::string_view, sizeof...(Is) + 1> {
static std::array<detail::string_view, sizeof...(Is) + 1>
arr{"", enum_type::template name<Enum, Is>()...};
static std::array<detail::string_view, sizeof...(Is) + 1> arr{
"", enum_type::template name<Enum, Is>()...};
return arr;
}

template <typename Enum, int Min, int Max>
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>());
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 +404,68 @@ inline auto to_enum_impl(detail::string_view str) noexcept
}

template <typename Enum, int Min, int Max>
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) {
auto maybe_enum =
to_enum_impl<Enum, Min, Max>(str.substr(index, i - index));
if (maybe_enum.has_value()) {
result.emplace(result ? static_cast<Enum>(*result | *maybe_enum)
: *maybe_enum);
}

if (str[i] == '|') {
index = i + 1U;
}
}

auto maybe_enum =
to_enum_impl<Enum, Min, Max>(str.substr(index, str.size() - index));
if (maybe_enum.has_value()) {
result.emplace(result ? static_cast<Enum>(*result | *maybe_enum)
: *maybe_enum);
}
return result;
}
}

template <typename Enum, int Min, int Max,
detail::enable_if_t<!detail::has_bit_or<Enum>::value, bool> = true>
inline auto enum_name_impl(Enum e) noexcept -> detail::string_view {
auto arr = get_enum_array<Enum>(detail::make_enum_sequence<Enum, Min, Max>());
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)};
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>
inline auto enum_name_impl(Enum e) noexcept -> detail::string_or_view_t<Enum> {
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)};
const auto name = arr[(index < Min || index > arr.size() - 1) ? 0 : index];

if (!name.empty() && !std::isdigit(name[0])) {
return std::string{name};
} else {
auto bitmasked_name = std::string{};

for (auto i{Min}; i < Max; ++i) {
const auto idx = std::abs(Min) + i + (Min < 0 ? 1 : 1);
if (!(idx < Min || idx > arr.size() - 1) && !arr[idx].empty() &&
!std::isdigit(arr[idx][0]) && (e & static_cast<Enum>(i)) == static_cast<Enum>(i)) {
bitmasked_name.append(arr[idx]).append("|");
}
}
return bitmasked_name.substr(0U, bitmasked_name.size() - 1);
}
}
} // namespace detail
} // namespace mgutility

Expand Down Expand Up @@ -462,7 +539,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 +548,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 +561,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 Down

0 comments on commit 20c043a

Please sign in to comment.