Skip to content

Commit

Permalink
Added support for flag enums; handle unidentified enums
Browse files Browse the repository at this point in the history
  • Loading branch information
liuzicheng1987 committed Dec 14, 2023
1 parent 79c24ad commit a547abd
Show file tree
Hide file tree
Showing 16 changed files with 496 additions and 112 deletions.
101 changes: 97 additions & 4 deletions docs/enums.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Enums

reflect-cpp supports scoped enumerations.
reflect-cpp supports scoped enumerations. They can either come in the form of normal enumerations or flag enums.

## Normal enumerations

Example:

Expand Down Expand Up @@ -38,7 +40,7 @@ enum struct Color2 { red, green, blue, yellow };
enum Color3 { red, green, blue, yellow };
```
2. You cannot have more than 100 values and if you explicitly assign values, they must be between 0 and 99.
2. You cannot have more than 128 values and if you explicitly assign values, they must be between 0 and 127.
```cpp
/// OK
Expand All @@ -50,6 +52,97 @@ enum struct Color2 { red, green, blue = 50, yellow };
/// unsupported - red is a negative value
enum Color3 { red = -10, green, blue, yellow };
/// unsupported - red is greater than 99
/// unsupported - red is greater than 127
enum Color3 { red = 200, green, blue, yellow };
```
```

## Flag enums

Sometimes the enumerations are not mutually exclusive - sticking with the metaphor of colors, it is perfectly
possible for things to have more than one color. C++ programmers sometimes like to model this using a flag enum.

Flag enums work as follows:

1. The bitwise OR operator must be defined on them. If an enum has the bitwise OR operator defined on it, it will be treated as a flag enum.
2. The most important values of the enum must be 1 or 2^N, N being a positive integer (in other words 1,2,4,8,16,32,...).

Example:

```cpp
// The important colors must be 1 or 2^N
enum class Color {
red = 256,
green = 512,
blue = 1024,
yellow = 2048,
orange = (256 | 2048) // red + yellow = orange
};

// The bitwise OR operator must be defined - this is how reflect-cpp knows that
// this is a flag enum.
inline Color operator|(Color c1, Color c2) {
return static_cast<Color>(static_cast<int>(c1) | static_cast<int>(c2));
}
```
In this particular example, the important colors red, green, blue and yellow are all in the form of 2^N.
Other colors are ok as well, if they are expressed as combinations of the 2^N-colors.
When something is a flag enum, then you can also do this:
```cpp
const auto circle =
Circle{.radius = 2.0, .color = Color::blue | Color::green};
```

Which will then be represented as follows:

```json
{"radius":2.0,"color":"blue|green"}
```

Using orange is fine as well:

```cpp
const auto circle =
Circle{.radius = 2.0, .color = Color::orange};
```
But it will be represented in terms of the base colors (which are in the form 2^N):
```json
{"radius":2.0,"color":"red|yellow"}
```

You can also combine orange with another color:

```cpp
const auto circle =
Circle{.radius = 2.0, .color = Color::blue | Color::orange};
```
Which will result in this:
```json
{"radius":2.0,"color":"red|blue|yellow"}
```

## What happens if an enum cannot be matched?

If an enum cannot be matched, it will be written as an integer. This works for both normal enums as well as flag enums.

For instance, if you use the `Color` flag enum as described in the previous section, you can do something like this:

```cpp
// Enums are not supposed to be used like this, but it is defined behavior for scoped enumerations.
const auto circle = Circle{.radius = 2.0, .color = static_cast<Color>(10000)};
```
This will be represented as follows:
```json
{"radius":2.0,"color":"16|red|green|blue|8192"}
```

This works, because 16 + 256 + 512 + 1024 + 8192 = 10000. Flag enums are *always* represented in terms of 2^N-numbers.

60 changes: 0 additions & 60 deletions include/rfl/internal/enums/LiteralConverter.hpp

This file was deleted.

12 changes: 10 additions & 2 deletions include/rfl/internal/enums/Names.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
#include <type_traits>

#include "rfl/Literal.hpp"
#include "rfl/define_literal.hpp"

namespace rfl {
namespace internal {
namespace enums {

template <class EnumType, class LiteralType, size_t N>
template <class EnumType, class LiteralType, size_t N, auto... _enums>
struct Names {
/// Contains a collection of enums as compile-time strings.
using Literal = LiteralType;
Expand All @@ -22,10 +23,17 @@ struct Names {
constexpr static size_t size = N;

/// A list of all the possible enums
std::array<EnumType, N> enums_;
constexpr static std::array<EnumType, N> enums_ =
std::array<EnumType, N>{_enums...};

static_assert(N == 0 || LiteralType::size() == N,
"Size of literal and enum do not match.");

template <class NewLiteral, auto _new_enum>
using AddOneType = std::conditional_t<
N == 0, Names<EnumType, NewLiteral, 1, _new_enum>,
Names<EnumType, define_literal_t<LiteralType, NewLiteral>, N + 1,
_enums..., _new_enum>>;
};

} // namespace enums
Expand Down
132 changes: 132 additions & 0 deletions include/rfl/internal/enums/StringConverter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#ifndef RFL_INTERNAL_ENUMS_STRINGCONVERTER_HPP_
#define RFL_INTERNAL_ENUMS_STRINGCONVERTER_HPP_

#include <algorithm>
#include <array>
#include <string>
#include <string_view>
#include <type_traits>

#include "rfl/Result.hpp"
#include "rfl/internal/enums/get_enum_names.hpp"
#include "rfl/internal/enums/is_flag_enum.hpp"
#include "rfl/internal/strings/join.hpp"
#include "rfl/internal/strings/split.hpp"
#include "rfl/type_name_t.hpp"

namespace rfl {
namespace internal {
namespace enums {

template <class EnumType>
class StringConverter {
private:
static constexpr bool is_flag_enum_ = is_flag_enum<EnumType>;

static constexpr auto names_ = get_enum_names<EnumType, is_flag_enum_>();

using NamesLiteral = typename decltype(names_)::Literal;

public:
/// Transform an enum to a matching string.
static std::string enum_to_string(const EnumType _enum) {
if constexpr (is_flag_enum_) {
return flag_enum_to_string(_enum);
} else {
return enum_to_single_string(_enum);
}
}

/// Transforms a string to the matching enum.
static Result<EnumType> string_to_enum(const std::string& _str) {
static_assert(names_.size != 0,
"No enum could be identified. Please choose enum values "
"between 0 to 127 or for flag enums choose 1,2,4,8,16,...");
if constexpr (is_flag_enum_) {
return string_to_flag_enum(_str);
} else {
return single_string_to_enum(_str);
}
}

private:
/// Iterates through the enum bit by bit and matches it against the flags.
static std::string flag_enum_to_string(const EnumType _e) {
using T = std::underlying_type_t<EnumType>;
auto val = static_cast<T>(_e);
int i = 0;
std::vector<std::string> flags;
while (val != 0) {
const auto bit = val & static_cast<T>(1);
if (bit == 1) {
auto str = enum_to_single_string(
static_cast<EnumType>(static_cast<T>(1) << i));
flags.emplace_back(std::move(str));
}
++i;
val >>= 1;
}
return strings::join("|", flags);
}

/// This assumes that _enum can be exactly matched to one of the names and
/// does not have to be combined using |.
static std::string enum_to_single_string(const EnumType _enum) {
const auto to_str = [](const auto _l) { return _l.str(); };

for (size_t i = 0; i < names_.size; ++i) {
if (names_.enums_[i] == _enum) {
return NamesLiteral::from_value(
static_cast<typename NamesLiteral::ValueType>(i))
.transform(to_str)
.value();
}
}

return std::to_string(static_cast<std::underlying_type_t<EnumType>>(_enum));
}

/// Finds the enum matching the literal.
static EnumType literal_to_enum(const NamesLiteral _lit) noexcept {
return names_.enums_[_lit.value()];
}

/// This assumes that _enum can be exactly matched to one of the names and
/// does not have to be combined using |.
static Result<EnumType> single_string_to_enum(const std::string& _str) {
const auto r = NamesLiteral::from_string(_str).transform(literal_to_enum);
if (r) {
return r;
} else {
try {
return static_cast<EnumType>(std::stoi(_str));
} catch (std::exception& exp) {
return Error(exp.what());
}
}
}

/// Only relevant if this is a flag enum - combines the different matches
/// using |.
static Result<EnumType> string_to_flag_enum(
const std::string& _str) noexcept {
using T = std::underlying_type_t<EnumType>;
const auto split = strings::split(_str, "|");
auto res = static_cast<T>(0);
for (const auto& s : split) {
const auto r = single_string_to_enum(s);
if (r) {
res |= static_cast<T>(*r);
} else {
return r;
}
}
return static_cast<EnumType>(res);
}
};

} // namespace enums
} // namespace internal
} // namespace rfl

#endif
Loading

0 comments on commit a547abd

Please sign in to comment.