General underlying types
The underlying type of a Better Enum doesn't have to be an integral type. It can
be any literal type T
, as long as you provide a constexpr
two-way mapping
between T
and an integral type of your choosing. It also works in C++98,
though, of course, T
doesn't have to be literal and the mapping doesn't have
to be constexpr
— everything will be done by Better Enums at run time.
Doing this enables the following usage:
// The type. A color triplet. struct html_color { uint8_t r, g, b; constexpr html_color(uint8_t _r, uint8_t g, uint8_t b) : r(_r), g(_g), b(_b) { } }; // The enum. ENUM(Color, html_color, darksalmon = 0xc47451, purplemimosa = 0x9e7bff, slimegreen = 0xbce954) // The usage. Color c = Color::darksalmon; std::cout << "Red component: " << c->r << std::endl; switch (c) { case Color::darksalmon: // ... case Color::purplemimosa: // ... case Color::slimegreen: // ... }
As you can see, you can have an enumerated set of any literal type, and safely
use the values in switch
, with the compiler checking exhaustiveness. You can
also access the type's members using the enum->underlying_member
syntax.
You do have to supply the mapping to an integral type, however. One option is:
// The mapping. It just stuffs bits. template <> struct ::better_enums::underlying_traits<html_color> { using integral_representation = unsigned int; constexpr static html_color from_integral(unsigned int i) { return html_color(i >> 16 & 0xff, i >> 8 & 0xff, i & 0xff); } constexpr static unsigned int to_integral(html_color c) { return (unsigned int)c.r << 16 | (unsigned int)c.g << 8 | c.b; } };
Using constructors in initializers
The declaration above used only numeric initializers. It is possible to use the
type's own constructors, provided the type has a constexpr
conversion to your
chosen integral type:
// The type. struct html_color { uint8_t r, g, b; constexpr html_color(uint8_t _r, uint8_t g, uint8_t b) : r(_r), g(_g), b(_b) { } // This is new: constexpr operator unsigned int() const { return (unsigned int)r << 16 | (unsigned int)g << 8 | b; } }; // The enum. ENUM(Color, html_color, darksalmon = 0xc47451, purplemimosa = 0x9e7bff, slimegreen = 0xbce954, celeste = html_color(0x50, 0xeb, 0xec))
This is not possible at all in C++98, however.
Letting the compiler enumerate your literal type
You don't have to use initializers. For example, as long as your example type
file_descriptor
knows how to deal with the values, you can have the compiler
generate them in sequence:
ENUM(FD, file_descriptor, STDIN, STDOUT, STDERR, SomePipeYourDaemonHas, ...)
SAMPLE
You can see the code "in action" in the test case. Be aware that it's not very "nice," because it uses conditional compilation to run under multiple compilers. I haven't written a clean sample or documentation yet simply because this feature is in a very early stage of development.
Discussion
This feature is still semi-experimental, though I expect it to remain stable,
except perhaps that I will make it possible to infer the type
integral_representation
.
Any opinions are welcome.
- The main reason Better Enums needs you to supply and explicit mapping is
because it can't just get the "bits" of objects of underlying type in
constexpr
code. Bothreinterpret_cast
and union abuse seem to be forbidden inconstexpr
functions. - There is currently no way to have two different integral representaitons for the same underlying type in different enums. I don't think that's a major use case at this point, however.