better-enums/doc/OptInFeatures.md
Anton Bachin 2acb5743fa Complete documentation and testing overhaul.
The documentation is now generated from markdown. Samples are generated from the
tutorial pages. Testing is done by a Python script which runs the tests for a
large number of compilers.

This version is not very developer-friendly - the Python scripts need ways of
limiting what compilers they try to run. If you don't have 15 compilers
installed, you won't be able to run the tests in this commit. Fix coming soon.
2015-05-27 09:58:34 -05:00

4.7 KiB

Opt-in features

Better Enums has a few opt-in features that affect how enums are generated. The default configuration is suitable for general use, but you might have more stringent requirements. This page describes the optional features and how to enable them.

Strict conversions

Each Better Enum is implicitly convertible to its member enum _enumerated type. This is meant to support usage of Better Enums directly in switch statements. When you write

switch (channel) {
    ...
}

the compiler applies the implicit conversion, and is also able to do case exhaustiveness checking. Unfortunately, _enumerated is always a regular $cxx98 enum type, meaning that it has standard conversions to integral types. The end result is channel is implicitly convertible all the way to int — behavior that is often considered a violation of type safety.

In $cxx11, you can force Better Enums to declare an internal enum class type to use for switch statements. Each enum will then be only convertible to its enum class, and won't be implicitly convertible to integers. This is done by defining BETTER_ENUMS_STRICT_CONVERSION before including enum.h. You would typically do this on the compiler's command line.

The reason strict conversions aren't enabled by default in $cxx11 is that doing so would break compatibility with the $cxx98 interface.

  • The "weaker" incompatibility is that you could write a bunch of $cxx98 code that relies on implicit integer conversions, and then try to switch to $cxx11. The code would then fail to compile. An example where implicit conversions are an "advantage" is when using Better Enums as arguments to the methods of std::bitset. I could have ignored this problem by declaring usage of implicit integer conversions unsupported, but in light of the next issue, I decided not to do that.
  • The "stronger" incompatibility is a difference in how switch cases must be written. The syntaxes for the two variants, implicitly-converting and strict, are mutually exclusive.

Here they are:

// Default variant
switch (channel) {
    case Channel::Red:   break;
    case Channel::Green: break;
    case Channel::Blue:  break;
}

// Strict variant
switch (channel) {
    case +Channel::Red:   break;
    case +Channel::Green: break;
    case +Channel::Blue:  break;
}

I would be very happy to make conversion to enum class the default whenever enum class is available, but how to do so without breaking compatibility is so far an open problem.

Compile-time name trimming

Better Enums is able to do all of its work at compile time. There is one task, however, at which my current method is pretty slow on modern compilers. The performance is still reasonable, but it makes enums take about four times longer to compile, compared to deferring the task to program startup. The task is trimming stringized constant names.

The problem arises when the preprocessor stringizes an initializer. For example,

ENUM(Channel, int, Red = 1, Green, Blue);

results in an internal array, somewhere inside the generated Better Enum, that looks like

names = {"Red = 1", "Green", "Blue"}

Before the name of Channel::Red can be returned to the user, the initializer = 1 must be trimmed off. This is the part that is slow to compile, and is deferred to startup by default.

If you want to enable it at compile time, you have two options. The first is to use an alternative SLOW_ENUM macro to declare your enum. It will enable compile-time trimming for that enum only. If you only do this for a few enums, you probably won't notice the difference in compilation time.

You can also enable compile-time trimming globally for all enums by defining BETTER_ENUMS_CONSTEXPR_TO_STRING before including enum.h. Typically, you would do this by supplying a command-line argument to your compiler.

The result of doing either one is that _to_string and _names will become constexpr for your compile-time enjoyment.

The reason this option is not enabled by default when avaialble, besides the fact that the current implementation is slow, is that I don't believe most users need it most of the time.

As a note, the performance is not that bad. You still have to define on the order of 10+ slow enums in order to slow compilation down as much as merely including iostream does. However, it is shockingly slower than the faster implementation, where you have the leeway to define 40+ enums before you reach the same level of slowdown as iostream gives you.

There are enough other problems with slow enums, however, like potential symbol pollution in the final binaries, that I decided to leave them as an opt-in feature until they improve to the point where they can be the default.