Skip to content
Serge Aleynikov edited this page Feb 18, 2021 · 36 revisions

Reflectable Enum

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 called UNDEFINED with value 0)
  • (Type, UndefValue) (the default value is called UNDEFINED = UndefValue)
  • (Type, UndefName, DefValue) (the undefined/default value is UndefName = DefValue)
  • (Type, UndefName, DefValue, FirstValue) (same as above, and the first value is FirstValue)
  • (Type, UndefName, DefValue, FirstValue, Explicit) (same as above, and boolean Explicit (default=true) enforces explicit conversion from Type)

Limitations:

  • Enumerations defined using UTXX_ENUM cannot have values assigned to them with '=' operator in the definition body of the macro. If you require value assignments, use UTXX_ENUMV macro documented in the next section below. UTXX_ENUM(MyEnumT, A = 0, B, C); will still be able to compile, but MyEnumT::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
);

Reflectable Enum With Custom Symbolic Values

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

Reflectable Enum With Assigned Values

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 called UNDEFINED with value 0)
  • (ValueType, InitValue) (the undefined member is called UNDEFINED = 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; });

Reflectable Flags

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

Clone this wiki locally