-
Notifications
You must be signed in to change notification settings - Fork 0
/
VariantEnum.hpp
462 lines (329 loc) · 16.2 KB
/
VariantEnum.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
#pragma once
#include <limits> // for std::numeric_limits<T>::max()
#include <ino/meta/traits/is_unique.hpp>
#include <ino/meta/traits/contains.hpp>
#include <ino/meta/traits/index_of.hpp>
#include <ino/meta/VariantEnum/traits/is_all_enums.hpp>
#include <ino/meta/VariantEnum/traits/get_uint_type.hpp>
#include <ino/meta/VariantEnum/traits/biggest_underlying_type.hpp>
namespace ino {
namespace meta {
namespace variant_enum {
/**
# Example of usage
@code{.cpp}
using VarEnum = VariantEnum<
Fruits,
Colors,
Characters
>;
VarEnum value(Colors::Blue);
switch (value.type_index()) {
case VarEnum::index_of<Fruits>(): {
std::cout << "Fruit: " << value.get<Fruits>() << std::endl;
} break;
case VarEnum::index_of<Colors>(): {
std::cout << "Color: " << value.get<Colors>() << std::endl;
} break;
case VarEnum::index_of<Characters>(): {
std::cout << "Character: " << value.get<Character>() << std::endl;
} break;
default: {
std::cout << "Undefined" << std::endl;
} break;
}
@endcode
*/
template <typename ... Enums>
class VariantEnum
{
// Make sure, that each of Enums type is enum
static_assert(traits::is_all_enums<Enums...>::value == true, "All Enums types must be enums");
// Make sure, that every Enums types are unique
static_assert(ino::meta::traits::is_unique<Enums...>::value == true, "All Enums types must be unique");
// TODO: make sure, that every sizeof( std::underlying_type<Enum>() ) <= std::size_t(type_idx)
public:
static constexpr std::size_t enums_count = sizeof...(Enums);
static constexpr std::size_t npos = (enums_count + 1); // Undefined type idx
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/*
Typically, that type is 'std::uint8_t', except you pass greater then
255 (uint8_t::max()) enums count. If it's so, well ... 'may god help you'
and bless your compiler! :)
*/
using type_idx_t = typename traits::get_uint_type<npos>::type; // Or simply choose std::size_t
/*
Make sure, that 'types index' can store enough information.
For example, if we creates extremely huge (but still possible) variant:
VarinatEnum< Enums... up to 260 >
`_type_idx` type must be greater then std::uint8_t (since it's maximum
value is 255, but we need 261 (260 type indexes + 1 for undefined state),
*/
static_assert(std::numeric_limits<type_idx_t>::max() >= npos, "Type index cannot describe 'undefined' state");
// Extra paranoidal check, that `type_idx` is unsigned integral type (since 'signed part' unusable)
static_assert(std::is_unsigned<type_idx_t>::value == true, "Type index must be an unsigned integral type");
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/*
Dynamically deduced (integer) type, based on passed Enums types, and
... TODO: DESCRIBE ...
TODO: describe signed/unsigned enums combination safety
Reason - to not pick unnessary large type, like `std::uint64_t` for any
case, for economy.
*/
using raw_value_t = typename std::underlying_type<
typename traits::biggest_underlying_type<Enums...>::type
>::type; // std::uint64_t;
private:
type_idx_t _type_idx = npos; // Undefined by default
raw_value_t _value; // Undefined by default
public:
/// Constructs 'undefined' value
constexpr VariantEnum()
: _type_idx( npos ) // Undefined by default
, _value()
{}
/**
@brief Constructs some 'defined' value
@note Notice, that this c-tor not marked `explicit`, what allow us to write
code in two equivalent ways:
@code{.cpp}
VarEnum<Colors, Fruits> value(Color::red);
VarEnum<Colors, Fruits> value = Colors::red;
@endcode
*/
template <typename Enum>
constexpr VariantEnum(const Enum value)
: _type_idx( index_of<Enum>() )
, _value( static_cast< raw_value_t >(value) )
{
static_assert(is_type_allowed<Enum>() == true, "Unsupported enum type");
}
// -------------------------------------------------------------------------
// Explicetely named c-tors
static constexpr VariantEnum undefined() {
return VariantEnum();
}
template <typename Enum>
static constexpr VariantEnum fromValue(const Enum value) {
return VariantEnum(value);
}
// -------------------------------------------------------------------------
template <typename Enum>
static constexpr bool is_type_allowed() noexcept
{
static_assert(std::is_enum<Enum>::value == true, "Enum must be an enumeration type");
return ino::meta::traits::contains<Enum, Enums...>::value;
}
/// Returns index of enum type in that VariadicEnum
template <typename Enum>
static constexpr type_idx_t index_of() noexcept
{
static_assert(is_type_allowed<Enum>() == true, "Unsupported enum type");
return ino::meta::traits::index_of<Enum, Enums...>::value;
}
// -------------------------------------------------------------------------
/// Returns status - is value of some specific type (not undefined)
template <typename Enum>
constexpr bool is_type_of() const noexcept
{
static_assert(is_type_allowed<Enum>() == true, "Unsupported enum type");
return ( _type_idx == index_of<Enum>() );
}
/// Returns status - is value strictly the same as we pass
template <typename Enum>
constexpr bool is_value_of(const Enum value) const noexcept
{
return is_type_of<Enum>() && (get<Enum>() == value);
}
/// Returns status - is class contains any value
constexpr bool is_defined() const noexcept
{
return (_type_idx < npos);
}
/// Returns status - is object NOT contains any value
constexpr bool is_undefined() const noexcept
{
return !is_defined();
}
// -------------------------------------------------------------------------
template <typename Enum>
constexpr Enum get() const noexcept
{
static_assert(is_type_allowed<Enum>() == true, "Unsupported enum type");
return static_cast< Enum >(_value);
}
template <typename Enum>
inline VariantEnum& set(const Enum value) noexcept
{
static_assert(is_type_allowed<Enum>() == true, "Unsupported enum type");
_type_idx = index_of<Enum>();
_value = static_cast<raw_value_t>(value);
return (*this);
}
constexpr type_idx_t type_index() const noexcept {
return _type_idx;
}
template <typename Enum>
inline VariantEnum& operator = (const Enum value) noexcept {
return this->set(value);
}
// -------------------------------------------------------------------------
// Strict equality comparison check
constexpr bool operator == (const VariantEnum& other) const noexcept
{
return (_type_idx == other._type_idx) &&
(_value == other._value);
}
constexpr bool operator != (const VariantEnum& other) const noexcept
{
return !(*this == other);
}
};
} // namespace variant_enum
} // namespace meta
} // namespace ino
// -----------------------------------------------------------------------------
#if defined (INO_ENABLE_COMPILETIME_TESTS)
#pragma message "INO_ENABLE_COMPILETIME_TESTS: enabled in " __FILE__
namespace tests {
using namespace ino::meta::variant_enum;
enum class Fruits {
Orange = 0,
Apple,
Kiwi
};
enum class Colors {
Red = 0,
Green,
Blue,
Magenta,
Cyan
};
enum class Characters {
Neo = 0,
Morpheus,
Trinity,
Smith,
Oracle
};
enum class Unsupported {
Some = 0,
Unused,
Values
};
using VarEnum = VariantEnum<
Fruits,
Colors,
Characters
>;
namespace is_defined_tests {
static_assert( VariantEnum<Colors>().is_undefined() == true, "Variant of single enum type allowed");
static_assert( VarEnum().is_undefined() == true , "By default VariantEnum must be 'undefined'");
static_assert( VarEnum().is_defined() == false, "By default VariantEnum must be 'undefined'");
static_assert( VarEnum(Fruits::Apple).is_defined() == true, "Initialized by value - must be 'defined'");
static_assert( VarEnum(Fruits::Apple).is_undefined() == false, "Initialized by value - must be 'defined'");
static_assert( VarEnum(Colors::Green).is_defined() == true, "Initialized by value - must be 'defined'");
static_assert( VarEnum(Colors::Green).is_undefined() == false, "Initialized by value - must be 'defined'");
} // namespace is_defined_tests
namespace allowed_types_tests {
static_assert( VarEnum::is_type_allowed<Fruits >() == true, "Enums checking works correctly");
static_assert( VarEnum::is_type_allowed<Colors >() == true, "Enums checking works correctly");
static_assert( VarEnum::is_type_allowed<Characters>() == true, "Enums checking works correctly");
static_assert( VarEnum::is_type_allowed<Unsupported>() == false, "Enums checking works correctly");
} // namespace allowed_types_tests
namespace type_index_tests {
static_assert( VarEnum::index_of<Fruits>() == 0, "Enum type index deduced correctly");
static_assert( VarEnum::index_of<Colors>() == 1, "Enum type index deduced correctly");
static_assert( VarEnum::index_of<Characters>() == 2, "Enum type index deduced correctly");
} // namespace type_index_tests
namespace type_index_tests {
static_assert( VarEnum(Fruits::Apple ).type_index() == VarEnum::index_of<Fruits>(), "Enum type index correct");
static_assert( VarEnum(Colors::Magenta).type_index() == VarEnum::index_of<Colors>(), "Enum type index correct");
static_assert( VarEnum(Characters::Neo).type_index() == VarEnum::index_of<Characters>(), "Enum type index correct");
static_assert( VarEnum().type_index() != VarEnum::index_of<Fruits>(), "Enum initial type index correct");
static_assert( VarEnum().type_index() != VarEnum::index_of<Colors>(), "Enum initial type index correct");
static_assert( VarEnum().type_index() != VarEnum::index_of<Characters>(), "Enum initial type index correct");
} // namespace type_index_tests
namespace value_of_tests {
static_assert( VarEnum().is_value_of(Fruits::Apple) == false, "Enum initial value check correct");
static_assert( VarEnum().is_value_of(Colors::Magenta) == false, "Enum initial value check correct");
static_assert( VarEnum().is_value_of(Characters::Neo) == false, "Enum initial value check correct");
static_assert( VarEnum(Fruits::Apple).is_value_of(Fruits::Apple) == true, "Enum type index correct"); // Same enum type && same enum raw-value
static_assert( VarEnum(Fruits::Apple).is_value_of(Fruits::Kiwi) == false, "Enum type index correct"); // Same enum type && different enum raw-value
static_assert( VarEnum(Colors::Red ).is_value_of(Fruits::Orange) == false, "Enum type index correct"); // Different enum type && same enum raw-value
static_assert( VarEnum(Colors::Green).is_value_of(Fruits::Kiwi) == false, "Enum type index correct"); // Different enum type && different enum raw-value
} // namespace value_of_tests
static_assert( VarEnum(Fruits::Apple ).is_type_of<Fruits>() == true , "Value enum type stored correctly");
static_assert( VarEnum(Colors::Magenta).is_type_of<Colors>() == true , "Value enum type stored correctly");
static_assert( VarEnum(Colors::Green ).is_type_of<Characters>() == false, "Value enum type stored correctly");
namespace value_get_tests {
// Well... What you expect from 'undefined' value? Exception? (maybe not,
// since we may use this on embedded) Blow up? Well... Simply be careful.
static_assert( VarEnum().get<Colors>() == Colors::Red, "FFFUUUUU"); // Works, since 'Red' raw-type value is 0
static_assert( VarEnum().get<Colors>() != Colors::Green, "FFFUUUUU"); // Works, since 'Green' raw-type value is not 0
static_assert( VarEnum(Colors::Cyan).get<Colors>() == Colors::Cyan, "Value get works as expected");
// Well ... What you expect from types missmatch?
static_assert( VarEnum(Colors::Cyan).get<Characters>() == Characters::Oracle, "FFFUUUUU");
} // namespace value_get_tests
namespace comparison_tests {
static_assert( VarEnum() == VarEnum(), "Strict comparison of VariantEnums works");
static_assert( VarEnum() != VarEnum(Colors::Cyan), "Strict comparison of VariantEnums works");
static_assert( VarEnum(Colors::Cyan) == VarEnum(Colors::Cyan), "Strict comparison of VariantEnums works");
static_assert( VarEnum(Colors::Cyan) != VarEnum(Colors::Blue), "Strict comparison of VariantEnums works");
static_assert( VarEnum(Fruits::Kiwi) != VarEnum(Colors::Blue), "Strict comparison of VariantEnums works");
} // namespace comparison_tests
// =============================================================================
namespace internal_types_tests {
enum class Enum_u8 : std::uint8_t {
MAX = std::numeric_limits<std::uint8_t>::max(),
MIN = std::numeric_limits<std::uint8_t>::min()
};
enum class Enum_s8 : std::int8_t {
MAX = std::numeric_limits<std::int8_t>::max(),
MIN = std::numeric_limits<std::int8_t>::min()
};
enum class Enum_u16 : std::uint16_t {
MAX = std::numeric_limits<std::uint16_t>::max(),
MIN = std::numeric_limits<std::uint16_t>::min()
};
enum class Enum_s16 : std::int16_t {
MAX = std::numeric_limits<std::int16_t>::max(),
MIN = std::numeric_limits<std::int16_t>::min()
};
enum class Enum_u32 : std::uint32_t {
MAX = std::numeric_limits<std::uint32_t>::max(),
MIN = std::numeric_limits<std::uint32_t>::min()
};
enum class Enum_s32 : std::int32_t {
MAX = std::numeric_limits<std::int32_t>::max(),
MIN = std::numeric_limits<std::int32_t>::min()
};
enum class Enum_u64 : std::uint64_t {
MAX = std::numeric_limits<std::uint64_t>::max(),
MIN = std::numeric_limits<std::uint64_t>::min()
};
enum class Enum_s64 : std::int64_t {
MAX = std::numeric_limits<std::int64_t>::max(),
MIN = std::numeric_limits<std::int64_t>::min()
};
namespace type_index_type_tests {
static_assert(std::is_same< VariantEnum<Enum_u8>::type_idx_t, std::uint8_t >::value == true, "Test failed");
static_assert(std::is_same< VariantEnum<Enum_u8, Enum_u16>::type_idx_t, std::uint8_t >::value == true, "Test failed");
static_assert(std::is_same< VariantEnum<Enum_u8, Enum_u16, Enum_u32>::type_idx_t, std::uint8_t >::value == true, "Test failed");
// TODO: static_assert(std::is_same< VariantEnum<Enum_u8, Enum_u16, Enum_u32 ... Up To 256>::type_idx_t, std::uint8_t >::value == true, "Test failed");
} // namespace type_index_type_tests
namespace raw_value_type_tests {
static_assert(std::is_same< VariantEnum<Enum_u8>::raw_value_t, std::uint8_t>::value == true, "Test failed");
static_assert(std::is_same< VariantEnum<Enum_u8, Enum_u16>::raw_value_t, std::uint16_t>::value == true, "Test failed");
static_assert(std::is_same< VariantEnum<Enum_u8, Enum_u16, Enum_u32>::raw_value_t, std::uint32_t>::value == true, "Test failed");
// If enums different (signed/unsigned), but their sizes are equal, picked first type
static_assert(std::is_same< VariantEnum<Enum_u8, Enum_s8>::raw_value_t, std::uint8_t>::value == true, "Test failed");
static_assert(std::is_same< VariantEnum<Enum_s8, Enum_u8>::raw_value_t, std:: int8_t>::value == true, "Test failed");
// Even if 'raw' type is underlying_type< max(Enum_s8,Enum_u16)==Enum_u16 > == std::uint16_t, -128 saved/restored correctly
static_assert( VariantEnum<Enum_s8, Enum_u16>(Enum_s8::MIN).get<Enum_s8>() == Enum_s8::MIN, "Test failed");
static_assert( VariantEnum<Enum_s64, Enum_u64>(Enum_s64::MIN).get<Enum_s64>() == Enum_s64::MIN, "Test failed");
static_assert( VariantEnum<Enum_u64, Enum_s64>(Enum_u64::MAX).get<Enum_u64>() == Enum_u64::MAX, "Test failed");
} // namespace raw_value_type_tests
} // namespace internal_types_tests
} // namespace tests
#endif // INO_ENABLE_COMPILETIME_TESTS