Skip to content

Commit a547abd

Browse files
Added support for flag enums; handle unidentified enums
1 parent 79c24ad commit a547abd

16 files changed

+496
-112
lines changed

docs/enums.md

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Enums
22

3-
reflect-cpp supports scoped enumerations.
3+
reflect-cpp supports scoped enumerations. They can either come in the form of normal enumerations or flag enums.
4+
5+
## Normal enumerations
46

57
Example:
68

@@ -38,7 +40,7 @@ enum struct Color2 { red, green, blue, yellow };
3840
enum Color3 { red, green, blue, yellow };
3941
```
4042
41-
2. You cannot have more than 100 values and if you explicitly assign values, they must be between 0 and 99.
43+
2. You cannot have more than 128 values and if you explicitly assign values, they must be between 0 and 127.
4244
4345
```cpp
4446
/// OK
@@ -50,6 +52,97 @@ enum struct Color2 { red, green, blue = 50, yellow };
5052
/// unsupported - red is a negative value
5153
enum Color3 { red = -10, green, blue, yellow };
5254
53-
/// unsupported - red is greater than 99
55+
/// unsupported - red is greater than 127
5456
enum Color3 { red = 200, green, blue, yellow };
55-
```
57+
```
58+
59+
## Flag enums
60+
61+
Sometimes the enumerations are not mutually exclusive - sticking with the metaphor of colors, it is perfectly
62+
possible for things to have more than one color. C++ programmers sometimes like to model this using a flag enum.
63+
64+
Flag enums work as follows:
65+
66+
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.
67+
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,...).
68+
69+
Example:
70+
71+
```cpp
72+
// The important colors must be 1 or 2^N
73+
enum class Color {
74+
red = 256,
75+
green = 512,
76+
blue = 1024,
77+
yellow = 2048,
78+
orange = (256 | 2048) // red + yellow = orange
79+
};
80+
81+
// The bitwise OR operator must be defined - this is how reflect-cpp knows that
82+
// this is a flag enum.
83+
inline Color operator|(Color c1, Color c2) {
84+
return static_cast<Color>(static_cast<int>(c1) | static_cast<int>(c2));
85+
}
86+
```
87+
88+
In this particular example, the important colors red, green, blue and yellow are all in the form of 2^N.
89+
Other colors are ok as well, if they are expressed as combinations of the 2^N-colors.
90+
91+
When something is a flag enum, then you can also do this:
92+
93+
```cpp
94+
const auto circle =
95+
Circle{.radius = 2.0, .color = Color::blue | Color::green};
96+
```
97+
98+
Which will then be represented as follows:
99+
100+
```json
101+
{"radius":2.0,"color":"blue|green"}
102+
```
103+
104+
Using orange is fine as well:
105+
106+
```cpp
107+
const auto circle =
108+
Circle{.radius = 2.0, .color = Color::orange};
109+
```
110+
111+
But it will be represented in terms of the base colors (which are in the form 2^N):
112+
113+
```json
114+
{"radius":2.0,"color":"red|yellow"}
115+
```
116+
117+
You can also combine orange with another color:
118+
119+
```cpp
120+
const auto circle =
121+
Circle{.radius = 2.0, .color = Color::blue | Color::orange};
122+
```
123+
124+
Which will result in this:
125+
126+
```json
127+
{"radius":2.0,"color":"red|blue|yellow"}
128+
```
129+
130+
## What happens if an enum cannot be matched?
131+
132+
If an enum cannot be matched, it will be written as an integer. This works for both normal enums as well as flag enums.
133+
134+
For instance, if you use the `Color` flag enum as described in the previous section, you can do something like this:
135+
136+
```cpp
137+
// Enums are not supposed to be used like this, but it is defined behavior for scoped enumerations.
138+
const auto circle = Circle{.radius = 2.0, .color = static_cast<Color>(10000)};
139+
```
140+
141+
This will be represented as follows:
142+
143+
```json
144+
{"radius":2.0,"color":"16|red|green|blue|8192"}
145+
```
146+
147+
This works, because 16 + 256 + 512 + 1024 + 8192 = 10000. Flag enums are *always* represented in terms of 2^N-numbers.
148+

include/rfl/internal/enums/LiteralConverter.hpp

Lines changed: 0 additions & 60 deletions
This file was deleted.

include/rfl/internal/enums/Names.hpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
#include <type_traits>
99

1010
#include "rfl/Literal.hpp"
11+
#include "rfl/define_literal.hpp"
1112

1213
namespace rfl {
1314
namespace internal {
1415
namespace enums {
1516

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

2425
/// A list of all the possible enums
25-
std::array<EnumType, N> enums_;
26+
constexpr static std::array<EnumType, N> enums_ =
27+
std::array<EnumType, N>{_enums...};
2628

2729
static_assert(N == 0 || LiteralType::size() == N,
2830
"Size of literal and enum do not match.");
31+
32+
template <class NewLiteral, auto _new_enum>
33+
using AddOneType = std::conditional_t<
34+
N == 0, Names<EnumType, NewLiteral, 1, _new_enum>,
35+
Names<EnumType, define_literal_t<LiteralType, NewLiteral>, N + 1,
36+
_enums..., _new_enum>>;
2937
};
3038

3139
} // namespace enums
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#ifndef RFL_INTERNAL_ENUMS_STRINGCONVERTER_HPP_
2+
#define RFL_INTERNAL_ENUMS_STRINGCONVERTER_HPP_
3+
4+
#include <algorithm>
5+
#include <array>
6+
#include <string>
7+
#include <string_view>
8+
#include <type_traits>
9+
10+
#include "rfl/Result.hpp"
11+
#include "rfl/internal/enums/get_enum_names.hpp"
12+
#include "rfl/internal/enums/is_flag_enum.hpp"
13+
#include "rfl/internal/strings/join.hpp"
14+
#include "rfl/internal/strings/split.hpp"
15+
#include "rfl/type_name_t.hpp"
16+
17+
namespace rfl {
18+
namespace internal {
19+
namespace enums {
20+
21+
template <class EnumType>
22+
class StringConverter {
23+
private:
24+
static constexpr bool is_flag_enum_ = is_flag_enum<EnumType>;
25+
26+
static constexpr auto names_ = get_enum_names<EnumType, is_flag_enum_>();
27+
28+
using NamesLiteral = typename decltype(names_)::Literal;
29+
30+
public:
31+
/// Transform an enum to a matching string.
32+
static std::string enum_to_string(const EnumType _enum) {
33+
if constexpr (is_flag_enum_) {
34+
return flag_enum_to_string(_enum);
35+
} else {
36+
return enum_to_single_string(_enum);
37+
}
38+
}
39+
40+
/// Transforms a string to the matching enum.
41+
static Result<EnumType> string_to_enum(const std::string& _str) {
42+
static_assert(names_.size != 0,
43+
"No enum could be identified. Please choose enum values "
44+
"between 0 to 127 or for flag enums choose 1,2,4,8,16,...");
45+
if constexpr (is_flag_enum_) {
46+
return string_to_flag_enum(_str);
47+
} else {
48+
return single_string_to_enum(_str);
49+
}
50+
}
51+
52+
private:
53+
/// Iterates through the enum bit by bit and matches it against the flags.
54+
static std::string flag_enum_to_string(const EnumType _e) {
55+
using T = std::underlying_type_t<EnumType>;
56+
auto val = static_cast<T>(_e);
57+
int i = 0;
58+
std::vector<std::string> flags;
59+
while (val != 0) {
60+
const auto bit = val & static_cast<T>(1);
61+
if (bit == 1) {
62+
auto str = enum_to_single_string(
63+
static_cast<EnumType>(static_cast<T>(1) << i));
64+
flags.emplace_back(std::move(str));
65+
}
66+
++i;
67+
val >>= 1;
68+
}
69+
return strings::join("|", flags);
70+
}
71+
72+
/// This assumes that _enum can be exactly matched to one of the names and
73+
/// does not have to be combined using |.
74+
static std::string enum_to_single_string(const EnumType _enum) {
75+
const auto to_str = [](const auto _l) { return _l.str(); };
76+
77+
for (size_t i = 0; i < names_.size; ++i) {
78+
if (names_.enums_[i] == _enum) {
79+
return NamesLiteral::from_value(
80+
static_cast<typename NamesLiteral::ValueType>(i))
81+
.transform(to_str)
82+
.value();
83+
}
84+
}
85+
86+
return std::to_string(static_cast<std::underlying_type_t<EnumType>>(_enum));
87+
}
88+
89+
/// Finds the enum matching the literal.
90+
static EnumType literal_to_enum(const NamesLiteral _lit) noexcept {
91+
return names_.enums_[_lit.value()];
92+
}
93+
94+
/// This assumes that _enum can be exactly matched to one of the names and
95+
/// does not have to be combined using |.
96+
static Result<EnumType> single_string_to_enum(const std::string& _str) {
97+
const auto r = NamesLiteral::from_string(_str).transform(literal_to_enum);
98+
if (r) {
99+
return r;
100+
} else {
101+
try {
102+
return static_cast<EnumType>(std::stoi(_str));
103+
} catch (std::exception& exp) {
104+
return Error(exp.what());
105+
}
106+
}
107+
}
108+
109+
/// Only relevant if this is a flag enum - combines the different matches
110+
/// using |.
111+
static Result<EnumType> string_to_flag_enum(
112+
const std::string& _str) noexcept {
113+
using T = std::underlying_type_t<EnumType>;
114+
const auto split = strings::split(_str, "|");
115+
auto res = static_cast<T>(0);
116+
for (const auto& s : split) {
117+
const auto r = single_string_to_enum(s);
118+
if (r) {
119+
res |= static_cast<T>(*r);
120+
} else {
121+
return r;
122+
}
123+
}
124+
return static_cast<EnumType>(res);
125+
}
126+
};
127+
128+
} // namespace enums
129+
} // namespace internal
130+
} // namespace rfl
131+
132+
#endif

0 commit comments

Comments
 (0)