-
Notifications
You must be signed in to change notification settings - Fork 16
Reflectable enums
The library supports definition of enums that automatically define to/from string conversion function and allow streaming. That support is provided through UTXX_ENUM and UTXX_ENUMV macros that define meta information on an enumerator through the use of C++ preprocessor.
UTXX_ENUM(SomeEnum, Type, A, B, C);
is semantically equivalent to:
enum class SomeEnum : Type {UNDEFINED = 0, A, B, C};
It is possible to customize the name of the UNDEFINED value, as well as its initial value.
The Type parameter accepts one of five forms:
-
Type(interpreted as-is, the default value, is calledUNDEFINEDwith value 0) -
(Type, UndefValue)(the default value is calledUNDEFINED= UndefValue) -
(Type, UndefName, DefValue)(the undefined/default value isUndefName = DefValue) -
(Type, UndefName, DefValue, FirstValue)(same as above, and the first value isFirstValue) -
(Type, UndefName, DefValue, FirstValue, Explicit)(same as above, and booleanExplicit(default=true) enforces explicit conversion fromType)
Limitations:
- Enumerations defined using
UTXX_ENUMcannot have values assigned to them with '=' operator in the definition body of the macro. If you require value assignments, useUTXX_ENUMVmacro documented in the next section below.UTXX_ENUM(MyEnumT, A = 0, B, C);will still be able to compile, butMyEnumT::to_string(MyEnumT::A)will return"A = 0"instead of"A".- The number of items in the enumerator cannot exceed 64. This is the limitation of the BOOST preprocessor macros. If you need to define up to 256 items, use
UTXX_ENUMZ().
Example:
#include <utxx/enum.hpp>
// enum MyEnum : int { UNDEFINED = 0, ValueA, ValueB, ValueC }
UTXX_ENUM
(MyEnum, int,
ValueA,
ValueB,
ValueC
);
std::cout << MyEnum() << std::endl; // Prints: UNDEFINED
MyEnum val = MyEnum::ValueA;
std::cout << "EnumValue: " << val << std::endl;
std::cout << "FromString: " << MyEnum::from_string("ValueB") << std::endl;
// Iterate over all enum values of MyEnum:
MyEnum::for_each([](MyEnum a)
{ std::cout << "Value: " << a << std::endl; return true; });Also:
// enum MyEnum2 : int { UNDEFINED = -1, ValueA, ValueB, ValueC }
UTXX_ENUM
(MyEnum2, (int, -1),
ValueA,
ValueB,
ValueC
);
// enum MyEnum3 : int { Nil = -1, ValueA, ValueB, ValueC }
UTXX_ENUM
(MyEnum3, (int, Nil, -1),
ValueA,
ValueB,
ValueC
);
// enum MyEnum4 : int { Nil = -1, ValueA = 5, ValueB, ValueC }
UTXX_ENUM
(MyEnum4, (int, Nil, -1, 5),
ValueA,
ValueB,
ValueC
);UTXX_ENUM(EnumType, Type, ...)
The variadic arguments of enum members given to this macro can be in one of two forms each enumerating one or two element tuples:
- Comma-delimitted members. E.g.:
A, B, (C, "c"), D - Sequence of members. E.g.:
(A) (B) (C, "c"), (D)
When a member is a two-element tuple, the second value of the tuple defines the symbolic value of the enum member:
// Variadic definition of members:
UTXX_ENUM(MyEnumT,
(char, // This is enum storage type
UNDEFINED, // The "name" of the first (i.e. "undefined") item
-1), // "initial" enum value for "UNDEFINED" value
(Apple, "Gala"), // An item can optionally have an associated string value
Pear, // String value defaults to item's name (i.e. "Pear")
(Grape, "Fuji")
);
// Sequence definition of members:
UTXX_ENUM(MyEnumT,
(char, // This is enum storage type
UNDEFINED, // The "name" of the first (i.e. "undefined") item
-1), // "initial" enum value for "UNDEFINED" value
(Apple, "Gala") // An item can optionally have an associated string value
(Pear) // String value defaults to item's name (i.e. "Pear")
(Grape, "Fuji")
);Both forms are semantically equivalent to:
enum class MyEnumT : char {UNDEFINED = -1, Apple, Pear, Grape};
Note that a symbolic value is NOT the member's value, but a
description that may be obtained by calling the value() method.
The member's actual enum value is the starting value of this
enum type plus the position of item in the list.
The value() of a member defaults to its symbolic name.
MyEnumT v = MyEnumT::from_name("Apple");
std::cout << "Value: " << v.name() << std::endl; // Out: Apple
std::cout << "Value: " << v.value() << std::endl; // Out: Gala
std::cout << "Value: " << v.code() << std::endl; // Out: 0
std::cout << "Value: " << int(v) << std::endl; // Out: 0
std::cout << "Value: " << v.to_string() << std::endl; // Out: Gala
std::cout << "Value: " << v << std::endl; // Out: Gala
v = MyEnumT::from_string("Fuji");
std::cout << "Value: " << v.name() << std::endl; // Out: Grape
std::cout << "Value: " << v.value() << std::endl; // Out: Fuji
In addition to the UTXX_ENUM the utxx library provides two more macros UTXX_ENUMV
and UTXX_ENUMU in the utxx/enumv.hpp and utxx/enumu.hpp header files respectively.
These macros allow to define a reflectable enum that has custom values assigned to its members:
UTXX_ENUMV(SomeEnum, Type, (A, 'x', "Apple") (B, 'y') (C));
UTXX_ENUMU(SomeEnum, Type, (A, 'x', "Apple") (B, 'y') (C));
which is semantically equivalent to:
enum class SomeEnum : char {UNDEFINED = ' ', A = 'x', B = 'y', C};
The difference between UTXX_ENUMV and UTXX_ENUMU is that the later restricts the
enum values to be unique (e.g. enum EnumU { A = 1, B = 2 };), whereas the former permits
the use of non-unique values (e.g. enum EnumV { A = 1, B = 1 };).
UTXX_ENUMU is more efficient for enum name lookups by value (i.e. SomeEnum.name()) than
UTXX_ENUMV because the later uses a map for looking up the name rather than a switch
statement used by UTXX_ENUMU.
It is possible to customize the name of the UNDEFINED member, as well as its initial value.
The Type parameter accepts one of three forms:
-
ValueType(interpreted as-is, the undefined member, is calledUNDEFINEDwith value 0) -
(ValueType, InitValue)(the undefined member is calledUNDEFINED= InitValue) (ValueType, UndefinedName, InitValue)
Example:
#include <utxx/enumv.hpp>
UTXX_ENUMV(MyEnum,
(char, // This is enum storage type
Nil, // The "name" of the first (i.e. "undefined") item is Nil
-1, // "initial" enum value for "UNDEFINED" value
),
(Apple, 'x', "Fuji") // Item with a value and with name string "Fuji"
(Pear, 'y') // Item with value 'y' (name defaults to "Pear")
(Grape) // Grape's value will be equal to 'y'+1
);
std::cout << MyEnum() << std::endl; // Prints: UNDEFINED
MyEnum val = MyEnum::Apple;
std::cout << "Val: " << val << std::endl; // Val: Fuji
std::cout << "Val: " << MyEnum::from_name("Pear") << std::endl; // Val: y
std::cout << "Val: " << MyEnum::from_string("y").name() << std::endl; // Val: Pear
// Iterate over all enum values of MyEnum:
MyEnum::for_each([](MyEnum a)
{ std::cout << "Value: " << a.to_string() << std::endl; return true; });Similarly to definition of reflectable enums, the utxx library has a macro for defining reflectable enumerated flags. The header file defining that macro is utxx/enum_flags.hpp.
UTXX_ENUM_FLAGS(MyFlags, Type, Orange, Apple, Banana);
is semantically equivalent to:
enum class SomeEnum : Type {NONE=0, Orange=1, Apple=2, Banana=4};
A more advanced version of the macro allows to customize the name of the
empty member (which defaults to NONE in UTXX_ENUM_FLAGS), as well as
assign symbolic values to each member:
UTXX_ENUM_FLAGZ(FlagsName, Type, EmptyMemberName, SequenceOfMembers);
where SequenceOfMembers is a sequence of tuples with up to two members
each, in the form:
- (MemberName)
- (MemberName, MemberValueString)
Example:
#include <utxx/enum_flags.hpp>
UTXX_ENUM_FLAGS
(MyFlags, int,
Orange,
Apple,
Banana
);
std::cout << MyFlags() << std::endl; // Prints: NONE
MyFlags flags(MyFlags::Orange, MyFlags::Apple);
cout << "Flags : " << flags << endl; // Flags : Orange|Apple
cout << "HasApple : " << flags.has(MyFlags::Apple) << endl; // HasApple : 1
cout << "HasBanana: " << flags.has(MyFlags::Banana) << endl; // HasBanana: 0
cout << "HasAll : " << flags.has_all(MyFlags::Apple|MyFlags::Banana) << endl; // HasAll : 0
cout << "FromStr : " << MyFlags::from_string("Apple | Banana") << endl; // FromStr : Apple|Banana
UTXX_ENUM_FLAGZ
(MyEnumT, char, Nil,
(Apple, "A") // Flags can optionally have string value.
(Pear, "PP")
(Grape) // String value defaults to "Grape"
(Orange)
);
MyEnumT val = MyEnumT::from_string("PP|Orange");
std::cout << "Value: " << val.to_string() << std::endl; // Prints: PP|Orange
std::cout << "Value: " << val.names() << std::endl; // Prints: Pear|Orange
std::cout << "Value: " << val.values() << std::endl; // Prints: PP|Orange
std::cout << "Value: " << val << std::endl; // Prints: Pear|Orange