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
constexprcode. Bothreinterpret_castand union abuse seem to be forbidden inconstexprfunctions. - 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.