Enhanced Enum – The guide

Motivation

The native C++ enums are a good choice for types labeling choices from a limited set. They are

  • Type safe: It’s hard for a programmer to accidentally create an enum object holding a value not in the predetermined set.

  • Lightweight: Under the hood enum is just an integer

They also have restrictions which sometimes makes them tedious to work with:

  • Enum is just an integer. The concept an enum is modeling may well have a more natural representation, but with a native C++ enum that mapping is not implemented in the enum type itself.

  • They lack reflection support. Even iterating over (or should I say enumerating) the members of an enum type requires writing boilerplate and duplicating enumerator definitions.

This library attempts to solve those problems by helping creating enhanced enum types that are just as lightweight and type safe as native enums, but support the convenient features users of higher level languages have learned to expect from their enums.

The design goals are:

  • Standard compliant: The library doesn’t use compiler specific features to enable reflection capabilities.

  • IDE friendly: The library doesn’t use macros to generate the unavoidable boilerplate. The generated code is explicit and available for human and tool inspection.

  • Supporting modern C++ idioms: The library is constexpr correct, includes utilities for template programming and type checks, etc.

  • Zero-cost: Ideally code manipulating enhanced enums should compile down to the same instructions as code manipulating native enums.

To give the enum types their capabilities without resorting to compiler hacks, it’s necessary to write some boilerplate accompanying the enum definitions. To aid with that the project includes EnumECG library that can be used to generate the necessary C++ code with Python. See EnumECG – The code generation support for more details.

Creating the enumeration

Warning

The generated code should not be edited, or the behavior of instantiating and using a class deriving from enum_base is undefined. The library makes assumptions about the types and functions used with the library. Those assumptions include but are not limited to:

  • The values of the label enumerators are zero-based integer sequence that can be used as indices in the values array.

  • The enhanced enum instantiates enum_base with correct template arguments, and has no non-static data members or non-empty bases.

As discussed if Motivation, some boilerplate is needed to define an enhanced enum type. The library tries to keep the generated boilerplate minimal, clean, and part of API. This means that the user of the generated enum type, and not just the library machinery, is free to use the types, constants and functions that the. Because the generated definitions are also public API of the type, backward incompatible changes are not made lightly.

Let’s create Status type that enumerates the different states of an imaginary process. The boilerplate can be generated with the EnumECG library, described in detail in EnumECG – The code generation support.

>>> import enum
>>> class Status(enum.Enum):
...     INITIALIZING = "initializing"
...     WAITING_FOR_INPUT = "waitingForInput"
...     BUSY = "busy"
>>> import enumecg
>>> enumecg.generate(Status)
'...'

The above command will generate the following C++ code:

 1enum class StatusLabel {
 2    INITIALIZING,
 3    WAITING_FOR_INPUT,
 4    BUSY,
 5};
 6
 7struct EnhancedStatus : ::enhanced_enum::enum_base<EnhancedStatus, StatusLabel, std::string_view> {
 8    using ::enhanced_enum::enum_base<EnhancedStatus, StatusLabel, std::string_view>::enum_base;
 9    static constexpr std::array values {
10        value_type { "initializing" },
11        value_type { "waitingForInput" },
12        value_type { "busy" },
13    };
14};
15
16constexpr EnhancedStatus enhance(StatusLabel e) noexcept
17{
18    return e;
19}
20
21namespace Statuses {
22inline constexpr const EnhancedStatus::value_type& INITIALIZING_VALUE { std::get<0>(EnhancedStatus::values) };
23inline constexpr const EnhancedStatus::value_type& WAITING_FOR_INPUT_VALUE { std::get<1>(EnhancedStatus::values) };
24inline constexpr const EnhancedStatus::value_type& BUSY_VALUE { std::get<2>(EnhancedStatus::values) };
25inline constexpr EnhancedStatus INITIALIZING { StatusLabel::INITIALIZING };
26inline constexpr EnhancedStatus WAITING_FOR_INPUT { StatusLabel::WAITING_FOR_INPUT };
27inline constexpr EnhancedStatus BUSY { StatusLabel::BUSY };
28inline constexpr auto begin() noexcept { return EnhancedStatus::begin();  }
29inline constexpr auto end() noexcept { return EnhancedStatus::end();  }
30inline constexpr auto all() noexcept { return EnhancedStatus::all();  }
31}

The generated boilerplate may appear in a namespace scope (global or any other namespace) in the user’s C++ files. In addition the file must include the enhanced_enum.hh header file.

Overview of the generated code

The code starts with the definition of enum class StatusLabel at line 1. This is the underlying label enum type. The label enumerators be thought as a names for the enumerators in the enhanced enum type.

The next block is the definition of struct EnhancedStatus at line 7. This is the actual enhance enum type. It derives from enum_base implemented in the Enhanced Enum library header. The base class has three template arguments:

  1. EnhancedStatus to employ the curiously recurring template pattern.

  2. StatusLabel, the label enum type

  3. std::string_view, the value type of the enumerators. They are discussed in more detail in Enumerator types and values.

The class also defines static data members mapping the enumerators to their values.

The library needs a way to map a label enumerators to the corresponding enhanced enumerators without knowing the name of the enhanced enum type. That is done with the enhance() method, defined at line 16. It needs to be defined in the same namespace as StatusLabel itself to support argument-dependent lookup. Because the library needs to reserve an identifier in the user namespace, there is a risk for name collision. The name enhance was chosen because, although short, it is a verb not otherwise often used in computer programming. Due to its shortness it makes the code using the function cleaner.

Finally the enumerators and their values are defined as constants in the namespace Statuses, defined at line 21. This associate namespace is not necessary for the library itself, but the application may use the constants and functions in this namespace for convenience.

Controlling the output

The above command generated names of the types, enumerators and the helper namespace from the names in the class Status. The defaults may not be what you want. Especially you might want to control if the label enum or the enhanced enum is the primary type and simply called Status.

For the details about controlling output see Enum definitions in detail.

Integrating to a project

The Enhanced Enum library does not provide integration between the EnumECG and the development environment out of box. It is recommended to use template based code generator tooling to include the bits generated by EnumECG to your C++ header files.

Here is an example using the awesome cog library. Write a header file like the following:

#include <enhanced_enum/enhanced_enum.hh>

#include <string_view>

namespace myapp {

/*[[[cog
import cog
import enumecg
import enum
class Status(enum.Enum):
    INITIALIZING = "initializing"
    WAITING_FOR_INPUT = "waitingForInput"
    BUSY = "busy"
cog.out(enumecg.generate(Status))
]]]*/
//[[[end]]]

}

Then, assuming you have cog installed in your environment, just invoke the command line utility and the enum boilerplate will appear where the template is located in the source file:

$ cog -r status.hh

cog supports both in-place code generation and writing the output to a file. There are advantages and disadvantages in both approaches. In-place code generation is IDE friendly and allows users that don’t have cog or EnumECG installed still compile your code, but care must be taken that the generated code is not changed manually. See the cog documentation for more details.

Using the enumeration

This section introduces how to use an enhanced enum type in your code, and the basic properties of an enhanced enum type. It is assumed that the code is the one generated in the previous section (Creating the enumeration).

  • Label type called StatusLabel

  • Enhanced enum type called EnhancedStatus

  • Function enhance() that can be used to promote a label enum into enhanced enum

  • Associate namespace Statuses containing enums and their values as constants

Basic properties

Enhanced enums, like the build-in C++ enums, are regular. They can be constructed and assigned from enhanced and label enums:

auto status = enhance(StatusLabel::INITIALIZING);
assert( status.get() == StatusLabel::INITIALIZING );
status = StatusLabel::BUSY;
assert( status.get() == StatusLabel::BUSY );

They have all comparison operators defined, and working transparently with both enhanced and label enum operands. Both the enhanced enums and label enums are totally ordered by the order the labels are declared in the code.

static_assert( Statuses::INITIALIZING == StatusLabel::INITIALIZING );
static_assert( StatusLabel::INITIALIZING < Statuses::BUSY );
// etc...

In C++20 the three-way comparison operator is defined between both label and enhanced enums.

static_assert( (Statuses::INITIALIZING <=> StatusLabel::INITIALIZING) == std::strong_ordering::equal );
static_assert( (StatusLabel::INITIALIZING <=> Statuses::BUSY) == std::strong_ordering::less );

Enumerator values and labels

Enhanced enumerators have values. They can be accessed using the value() function

static_assert( Statuses::INITIALIZING.value() == "initializing" );

Enumerators can be constructed from value using the static from() method:

static_assert( EnhancedStatus::from("initializing") == Statuses::INITIALIZING );

The underlying label enum can be accessed either with the get() method or explicit cast. Note that although label enum is implicitly convertible to enhanced enum, the converse is deliberately explicit.

static_assert( Statuses::INITIALIZING.get() == StatusLabel::INITIALIZING )
static_assert( static_cast<StatusLabel>(Statuses::INITIALIZING) == StatusLabel::INITIALIZING );

Enumerator ranges

The number of enumerators in an enhanced enum type can be queries by using the size() and ssize(), for unsigned and signed sizes, respectively.

A range containing all enumerators of a given enum type can be constructed with the static all() method:

for (const auto status : EnhancedStatus::all()) {
    // use status
}

The returned range can be used in compile time and has all the enumerators in the same order as they are declared in the type.

In C++20 the range returned by all() models a random access view, and can be used with other utilities of the ranges library.

const status_values_reversed =  EnhancedStatus::all() |
    std::ranges::views::reverse |
    std::ranges::views::transform([](const auto e) { return e.value(); });

For interfaces consuming iterator pairs, using begin() and end() may be more convenient:

std::for_each(
    EnhancedStatus::begin(), EnhancedStatus::end(),
    [](const auto status) { /* use status */ });

Note

The user should not assume an underlying type returned by the all(), begin() and end() functions, except that the iterators supports random access.

The iterators model the C++17 random access iterator concepts. The range returned by all() doesn’t model STL container. The intention is to remain forward-compatible with the view concepts from the Ranges TS. Unlike STL containers, views don’t define type aliases etc.

In C++20 the enumerator ranges implement std::ranges::view_interface, and are thus compatible with native views.

For convenience and readability, the associate namespace generated by the EnumECG library also contains aliases to the range functions:

for (const auto status : Statuses::all()) {
    // use status
}

std::for_each(
    Statuses::begin(), Statuses::end(),
    [](const auto status) { /* use status */ });

Hashes

The library defines a hash template for enhanced enumerations:

enhanced_enum::hash<EnhancedStatus> hasher;
for (const auto status : EnhancedStatus::all()) {
    std::cout << "Hash of " << status.value() << " is " << hasher(status) << "\n";
}

Due to restrictions of specializing the std::hash template in the standard library 1, the hash object is defined in the enhanced_enum namespace. But the standard library by default expects to find the definition of a hash object in the std namespace. You can do this yourself via inheritance:

namespace std {
    template<>
    struct hash<EnhancedStatus> : enhanced_enum::hash<EnhancedStatus> {};
}

std::unordered_map<EnhancedStatus, int> map;
map[Statuses::INITIALIZING] = 123;

Alternatively you can just instantiate the hash template explicitly when needed:

std::unordered_map<EnhancedStatus, int, enhanced_enum::hash<EnhancedStatus>> map;
map[Statuses::INITIALIZING] = 123;
1

Because the Enhanced Enum library doesn’t know about your types, the C++17 implementation relies on SFINAE to specialize templates for enhanced enumerations. But the standard library doesn’t allow SFINAE, it only allows partial specializations in the std namespace to rely on user defined types explicitly.

Library reference

The base class

template<typename EnhancedEnum, typename LabelEnum, typename ValueType>
struct enhanced_enum::enum_base

Base class for the enhanced enumeration types.

The essential functionality of an enhanced enum type is implemented in this class. A type gains the capabilities of an enhanced enum by deriving from this class, and defining a static constant array called values, as described in the user guide. The code for the derived class is intended to be autogenerated to ensure conformance with the requirements.

There is an order-preserving isomorphism between the instances of EnhancedEnum, and the enumerators of the underlying LabelEnum. Thus, conceptually the instances of EnhancedEnum are its enumerators, and will be called so in the documentation.

An enumerator of EnhancedEnum simply stores the value of its label enumerator as its only member. Consequently, most of the traits of LabelEnum are also traits of the EnhancedEnum type: enhanced enumerations are regular, totally ordered, trivial, and layout compatible with their underlying label enumerators.

It is assumed that the definition of the derived class is compatible with the requirements imposed by the library. Most notably the derived class must not have any non-static data members or other non-empty base classes. The intended way to ensure compatibility is to autogenerate the definition.

Warning

Trying to use an instance of a class derived from enum_base not meeting the requirements imposed by the library results in undefined behavior.

Template Parameters
  • EnhancedEnum – The enhanced enumeration. The base class template uses curiously recurring template pattern.

  • LabelEnum – The underlying enumeration (enum class)

  • ValueType – The enumerator value type

Public Types

using label_type = LabelEnum

Alias for the label enum type.

using value_type = ValueType

Alias for the value type.

Public Functions

enum_base() = default

Default constructor.

Construct an enumerator with indeterminate value

constexpr enum_base(const enum_base &other) noexcept = default

Copy construct an enumerator.

Postcondition: this->get() == other.get()

Parameters

other – The source enumerator

inline constexpr enum_base(const label_type &label) noexcept

Construct an enumerator with the given label.

Postcondition: this->get() == label.

Parameters

label – The label enumerator

constexpr enum_base &operator=(const enum_base &other) noexcept = default

Copy assign an enumerator.

Postcondition: this->get() == other.get()

Parameters

other – The source enumerator

Returns

*this

inline constexpr label_type get() const noexcept

Return the label enumerator.

inline explicit constexpr operator label_type() const noexcept

Convert the enumerator to its underlying label enumerator.

Returns

this->get()

inline constexpr const value_type &value() const noexcept

Return the value of the enumerator.

Throws

std::out_of_range – if *this is not a valid enumerator

Public Static Functions

static inline constexpr std::size_t size() noexcept

Return the size of the enumeration.

static inline constexpr std::ptrdiff_t ssize() noexcept

Return the signed size of the enumeration.

static inline constexpr auto begin() noexcept

Return iterator to the first enumerator.

Returns

A random access iterator to the beginning of the range containing all enumerators in the order they are declared in the enum

static inline constexpr auto end() noexcept

Return iterator to one past the last enumerator.

Returns

Iterator to the end of the range pointed to by begin()

static inline constexpr auto all() noexcept

Return range over all enumerators.

Returns

A range containing all enumerators in the order they are declared in the enum

static inline constexpr std::optional<EnhancedEnum> from(const value_type &value) noexcept

Return the enumerator with the given value.

Note

The number of comparisons is linear in the size of the enumeration. The assumption is that the number of enumerators is small and the values are localized in memory, making linear algorithm efficient in practice.

Parameters

value – The value to search

Returns

The first enumerator whose value is value, or empty if no such enumerator exists

Template support

group templatesupport

Support for writing templates with label enumerations and enhanced enumerations.

Typedefs

using enhanced = decltype(enhance(std::declval<LabelEnum>()))

Convert a label enumeration to an enhanced enumeration.

Template Parameters

LabelEnum – A label enum type (enum class)

using make_enhanced_t = typename make_enhanced<Enum>::type

Shorthand for make_enhanced.

Functions

template<typename Enum>
constexpr make_enhanced_t<Enum> ensure_enhanced(Enum e) noexcept

Return enhanced enumerator associated with the argument.

See also

is_same_when_enhanced for an example how this might be used in generic code

Parameters

e – The enumerator to promote

Returns

If Enum is a label enumeration, promote e to the associated enhanced enumerator. If Enum is an enhanced enumeration, return e as is.

Variables

template<typename T>
constexpr bool is_enhanced_enum_v = is_enhanced_enum<T>::value

Shorthand for is_enhanced_enum.

template<typename T>
constexpr bool is_label_enum_v = is_label_enum<T>::value

Shorthand for is_label_enum.

template<typename T, typename U>
constexpr bool is_same_when_enhanced_v = is_same_when_enhanced<T, U>::value

Shorthand for is_same_when_enhanced.

template<typename T>
struct is_enhanced_enum
#include <enhanced_enum.hh>

Check if a type in an enhanced enumeration.

If T is a type that derives from enum_base, then is_enhanced_enum derives from std::true_type. Otherwise derives from std::false_type.

Template Parameters

T – The type to check

template<typename T>
struct is_label_enum
#include <enhanced_enum.hh>

Check if a type in a label enumeration.

If T is a type that has an associated enhanced enum type (see enhanced), the is_label_enum derives from std::true_type. Otherwise derives from std::false_type.

Template Parameters

T – The type to check

template<typename Enum>
struct make_enhanced
#include <enhanced_enum.hh>

Makes an enumeration enhanced.

If Enum is either an enhanced enum (see is_enhanced_enum) or a label enum (see is_label_enum), has the associated enhanced enum as member typedef type. Otherwise has no member typedefs.

template<typename T, typename U>
struct is_same_when_enhanced
#include <enhanced_enum.hh>

Check if two types are the same once enhanced.

If T and U are either label enums or enhanced enums, and the associated enhanced enum types are the same, derives from std::true_type. Otherwise derives from std::false_type.

This template is useful for writing generic comparison functions that accepts both label and enhanced enums of the same kind.

template<
    typename Enum1, typename Enum2,
    typename = std::enable_if_t<is_same_when_enhanced_v<Enum1, Enum2>>
>
bool compare(Enum1 e1, Enum2 e2)
{
    // implementation can use ensure_enum(e1) to access the enhanced capabilities
}

Hash

template<typename Enum>
struct hash

Hash for enhanced enums.

This is a struct template satisfying the requirements of Hash for enhanced enum types. Instantiating the template is possibly if and only if is_enhanced_enum_v<Enum> == true.

Note

Due to restrictions of the C++ standard library, the enhanced enum library doesn’t specialize the std::hash template. Please see the user guide if you need a specialization of std::hash for your own type.