Better Enums

Reflective compile-time enums for C++

Open-source under the BSD license

Version 0.11.3

To install, just add enum.h to your project.

Visit the GitHub repo for issues, feedback, and the latest development.

Download enum.h GitHub

API reference

Contents

Overview

The declaration

#include <enum.h>
BETTER_ENUM(Enum, underlying_type, A, B, C, ...)

generates a new class type Enum which is notionally similar to the type created by this C++11 declaration:

enum class Enum : underlying_type {A, B, C, ...};

That is, Enum is a scoped enumerated type with constants Enum::A, Enum::B, Enum::C, and so on, with memory representation the same as underlying_type. It is possible to supply initializers for any of the constants:

BETTER_ENUM(Enum, underlying_type, A = 1, B = constant_expression, C = A, ...)

The initializers have the same meaning and constraints as in a built-in enum or enum class declaration.


The principal differences between the types declared by the BETTER_ENUM macro and enum class are:


The types produced by the BETTER_ENUM macro are called Better Enums in the rest of this reference.

Better Enums are similar to their underlying type for the purposes of argument passing. This means that they typically fit into a machine word, and should be passed by value.

All names declared in the scope of a Better Enum are prefixed with an underscore in order to avoid conflicts with potential constant names.

Running example

The rest of this reference uses the following declaration as a running example:

BETTER_ENUM(Enum, int, A, B, C)

Helper functions and types

The types and functions described here make it possible to use Better Enums with the rest of C++ in a reasonable fashion, or else they are referenced in the rest of the documentation.

typedef _enumerated

An internal type used to declare constants. The BETTER_ENUM macro generates something similar to

struct Enum {
    enum _enumerated : int {A, B, C};
    // ...
};

The user needs to be aware of _enumerated in only one situation. A literal constant such as Enum::A is an expression of type Enum::_enumerated, not Enum. It is not possible to directly call a method on the constant, as in Enum::A._to_string(). This problem is addressed by operator + below.

non-member constexpr Enum unary operator +(_enumerated)

Forces promotion of Enum::_enumerated to Enum. Provided to solve the problem described above. So:

// Does not compile
Enum::A._to_string()

// Compiles
(+Enum::A)._to_string()

constexpr implicit constructor Enum(_enumerated)

A constructor that performs implicit conversions of Enum::_enumerated to Enum. This allows code to use a literal constant where Enum is expected, and the compiler can do an implicit conversion. For example:

void do_something(Enum value);

do_something(+Enum::A);  // Not necessary
do_something(Enum::A);   // Implicit conversion available

Enum value = Enum::A;    // Implicit conversion

The other constructors of Enum are the implicitly-generated copy and move constructors. There is no default constructor. If you have comments on what a default constructor should do, please let me know.

non-member struct better_enums::optional<Enum>

An optional Enum value. These are returned by the various _nothrow functions, such as _from_string_nothrow. This type is meant to represent the possibility of failure. For example, suppose you have:

better_enums::optional<Enum>    maybe = _from_string_nothrow("A");

An optional value such as maybe is convertible to bool. If it converts to true, it holds a valid Enum value. Otherwise, if it converts to false, the operation that produced the optional value failed. So, you can continue with

if (maybe) {
    // The string conversion succeeded
    do_something(*maybe);
}
else {
    // The string conversion failed
}

As you can see, *maybe evaluates to the Enum value, in this case Enum::A.

The rest of this reference refers to this type as simply optional, as if you had entered

using optional = better_enums::optional<Enum>;

Value count and iteration

The types and members described here have to do with the sequence of constants declared, i.e. A, B, C in the running example.

static constexpr size_t _size()

The number of constants declared. Enum::_size() == 3.

static constexpr const size_t _size_constant

Same as _size, but a constant instead of a function. This is provided for use in C++98 constant expressions.

typedef _value_iterable

Type of object that permits iteration over the constants. Has at least constexpr begin(), end(), and size() methods, and constexpr operator[]. Iteration visits each declared constant, even if multiple constants have the same value, and visits them in order of declaration. See usage examples under _values.

typedef _value_iterator

Random-access iterator type for _value_iterable. Most operations, including dereferencing, are constexpr. The exceptions are mutating operators such as operator++. In constexpr code, that can be replaced with addition of 1. You typically don't have to refer to this type directly.

static constexpr _value_iterable _values()

constexpr access to the sequence of declared constants. For example:

for (size_t index = 0; index < Enum::_values().size(); ++index)
    do_something(Enum::_values()[index]);

or, using iterators:

for (Enum::_value_iterator iterator = Enum::_values().begin();
     iterator != Enum::_values().end(); ++iterator) {

    do_something(*iterator);
}

or, in C++11:

for (Enum value : Enum::_values())
    do_something(value);

String conversion and iteration

member constexpr? const char* _to_string() const

Returns the string representation a Better Enum value. For example:

Enum    value = Enum::A;
value._to_string();     // Same as "A".

If two or more constants have the same numeric value, it is undefined which name _to_string will choose, but it will choose one of them.

If value is not equal to the representation of any declared constant, for example if it was obtained using an unchecked conversion such as

Enum    value = Enum::_from_integral_unchecked(0xbadc0de);

then the behavior of value._to_string is undefined.

Running time is linear in the number of declared constants.

This method is not constexpr by default. Read here for information about making it constexpr.

static constexpr Enum _from_string(const char*)

If the given string is the exact name of a declared constant, returns the constant. Otherwise, throws std::runtime_error. Running time is linear in the number of declared constants multiplied by the length of the longest constant.

static constexpr optional<Enum> _from_string_nothrow(const char*)

Same as _from_string, but does not throw an exception on failure. Returns an optional value instead.

static constexpr Enum _from_string_nocase(const char*)

Same as _from_string, but comparison is up to case, in the usual sense in the Latin-1 encoding.

static constexpr optional<Enum> _from_string_nocase_nothrow(const char*)

Is to _from_string_nocase as _from_string_nothrow is to _from_string.

static constexpr bool _is_valid(const char*)

Evaluates to true if and only if the given string is the exact name of a declared constant. Running time is the same as for _from_string.

static constexpr bool _is_valid_nocase(const char*)

The same as _is_valid, but comparison is done up to case as in _from_string_nocase.

static constexpr const char* _name()

Evaluates to the name of the Better Enum type. Enum::_name() is the same string as "Enum".

typedef _name_iterable

Type of object that permits iteration over names of declared constants. Has at least constexpr begin(), end(), and size() methods. operator[] is also available, but is constexpr if and only if _to_string is constexpr. Iteration visits constants in order of declaration. See usage example under _names.

typedef _name_iterator

Random-access iterator type for _name_iterable. Most operations are constexpr, but dereferencing is constexpr if and only if _to_string is constexpr. Mutating operators such as operator++ are not constexpr due to their nature — adding 1 is a constexpr alternative. You typically don't have to refer to this type directly.

static constexpr? _name_iterable _names()

Access to the sequence of declared constant names. For example:

for (size_t index = 0; index < Enum::_names().size(); ++index)
    std::cout << Enum::_names()[index] << std::endl;

or, using iterators:

for (Enum::_name_iterator iterator = Enum::_names().begin();
     iterator != Enum::_names().end(); ++iterator) {

    std::cout << *iterator << std::endl;
}

or, in C++11:

for (const char *name : Enum::_names())
    std::cout << name << std::endl;

constexpr if and only if _to_string is constexpr.

Integer conversion

Better Enums are already represented as integers at run time. Values of the running example type Enum are the same as ints. However, Enum is a distinct type from int during type checking, the main difference being that its range of valid values is restricted to only the ones you have declared.

This section describes the various translations between Enum and int that are available. Each one translates the type, but at run time, most are no-ops, or validity checks followed by no-ops.

typedef _integral

The underlying or representation type of the Better Enum. For example, Enum::_integral is the same type as int. Each Better Enum has the same size and alignment requirement as its representation type.

member constexpr _integral _to_integral() const

No-op conversion of a Better Enum to a value of its representation type. For example,

(+Enum::C)._to_integral() == 2

Note that Better Enums are already implicitly convertible to their underlying integral types by default. You may still want to use this function, however, for clarity, and to ensure that your code remains compatible if the strict conversions feature is enabled later.

static constexpr Enum _from_integral(_integral)

Checked conversion of an integer to a Better Enum value. The check runs in time linear in the number of declared constants, but the conversion itself is a no-op. Throws std::runtime_error if the given integer is not the numeric value of one of the declared constants.

Enum::_from_integral(2);    // Enum::C
Enum::_from_integral(42);   // std::runtime_error

static constexpr optional<Enum> _from_integral_nothrow(_integral)

Checked conversion as _from_integral, but does not throw an exception on failure. Returns an optional value instead.

static constexpr Enum _from_integral_unchecked(_integral)

No-op unchecked conversion of an integer to a Better Enum value. If the given integer is not the numeric value of one of the declared constants, the behavior of all subsequent operations on the Better Enum value is undefined.

This is the direct inverse of _to_integral. Here are no-op round trips between int and Enum:

Enum::_from_integral_unchecked(value._to_integral());
Enum::_from_integral_unchecked(integer)._to_integral();

You should not use this function on untrusted input, however.

static constexpr bool _is_valid(_integral)

Evaluates to true if and only if the given integer is the numeric value of one of the declared constants. Running time is linear in the number of declared constants.

Index lookup

member constexpr std::size_t _to_index() const

Returns the index of a Better Enum value within its enum declaration. The index is determined from the value only; if two constants in the declaration have the same value, this function may return the index of either constant.

If the value does not correspond to any constant in the declaration (for example, if it was obtained using an unchecked conversion or a cast), then the behavior of value._to_index is undefined.

static constexpr Enum _from_index(size_t)

Returns the value of the constant with the given index. Throws std::runtime_error if not given the index of one of the constants.

static constexpr Enum _from_index_unchecked(size_t)

Returns the value of the constant with the given index. If not given one of the constants in the declaration of the enum, the returned value is undefined.

static constexpr optional<Enum> _from_index_nothrow(size_t)

Returns the value of the constant with the given index.

Stream operators

non-member std::ostream& operator <<(std::ostream&, const Enum&)

Formats the given enum to the given stream in the same way as _to_string.

non-member std::istream& operator >>(std::istream&, Enum&)

Reads from the given stream and attempts to parse an enum value in the same way as _from_string. In case of failure, sets the stream's failbit.

Hashing

macro BETTER_ENUMS_DECLARE_STD_HASH(Enum)

Use this outside namespace scope to declare a specialization of std::hash for the type Enum. For example:

// This declaration might be inside a namespace.
BETTER_ENUM(Channel, int, Red, Green, Blue)

// Later, outside the namespace:
BETTER_ENUMS_DECLARE_STD_HASH(Channel)