Here's an option for bitmasks if you don't actually have a use for the individual enum values (ex. you don't need to switch off of them)... and if you aren't worried about maintaining binary compatibility ie: you don't care where your bits live... which you probably are. Also you'd better not be too concerned with scoping and access control. Hmmm, enums have some nice properties for bit-fields... wonder if anyone has ever tried that :)
struct AnimalProperties
{
bool HasClaws : 1;
bool CanFly : 1;
bool EatsFish : 1;
bool Endangered : 1;
};
union AnimalDescription
{
AnimalProperties Properties;
int Flags;
};
void TestUnionFlags()
{
AnimalDescription propertiesA;
propertiesA.Properties.CanFly = true;
AnimalDescription propertiesB = propertiesA;
propertiesB.Properties.EatsFish = true;
if( propertiesA.Flags == propertiesB.Flags )
{
cout << "Life is terrible :(";
}
else
{
cout << "Life is great!";
}
AnimalDescription propertiesC = propertiesA;
if( propertiesA.Flags == propertiesC.Flags )
{
cout << "Life is great!";
}
else
{
cout << "Life is terrible :(";
}
}
We can see that life is great, we have our discrete values, and we have a nice int to & and | to our hearts content, which still has context of what its bits mean. Everything is consistent and predictable... for me... as long as I keep using Microsoft's VC++ compiler w/ Update 3 on Win10 x64 and don't touch my compiler flags :)
Even though everything is great... we have some context as to the meaning of flags now, since its in a union w/ the bitfield in the terrible real world where your program may be be responsible for more than a single discrete task you could still accidentally (quite easily) smash two flags fields of different unions together (say, AnimalProperties and ObjectProperties, since they're both ints), mixing up all yours bits, which is a horrible bug to trace down... and how I know many people on this post don't work with bitmasks very often, since building them is easy and maintaining them is hard.
class AnimalDefinition {
public:
static AnimalDefinition *GetAnimalDefinition( AnimalFlags flags ); //A little too obvious for my taste... NEXT!
static AnimalDefinition *GetAnimalDefinition( AnimalProperties properties ); //Oh I see how to use this! BORING, NEXT!
static AnimalDefinition *GetAnimalDefinition( int flags ); //hmm, wish I could see how to construct a valid "flags" int without CrossFingers+Ctrl+Shift+F("Animal*"). Maybe just hard-code 16 or something?
AnimalFlags animalFlags; //Well this is *way* too hard to break unintentionally, screw this!
int flags; //PERFECT! Nothing will ever go wrong here...
//wait, what values are used for this particular flags field? Is this AnimalFlags or ObjectFlags? Or is it RuntimePlatformFlags? Does it matter? Where's the documentation?
//Well luckily anyone in the code base and get confused and destroy the whole program! At least I don't need to static_cast anymore, phew!
private:
AnimalDescription m_description; //Oh I know what this is. All of the mystery and excitement of life has been stolen away :(
}
So then you make your union declaration private to prevent direct access to "Flags", and have to add getters/setters and operator overloads, then make a macro for all that, and you're basically right back where you started when you tried to do this with an Enum.
Unfortunately if you want your code to be portable, I don't think there is any way to either A) guarantee the bit layout or B) determine the bit layout at compile time (so you can track it and at least correct for changes across versions/platforms etc)
Offset in a struct with bit fields
At runtime you can play tricks w/ setting the the fields and XORing the flags to see which bits did change, sounds pretty crappy to me though verses having a 100% consistent, platform independent, and completely deterministic solution ie: an ENUM.
TL;DR:
Don't listen to the haters. C++ is not English. Just because the literal definition of an abbreviated keyword inherited from C might not fit your usage doesn't mean you shouldn't use it when the C and C++ definition of the keyword absolutely includes your use case. You can also use structs to model things other than structures, and classes for things other than school and social caste. You may use float for values which are grounded. You may use char for variables which are neither un-burnt nor a person in a novel, play, or movie. Any programmer who goes to the dictionary to determine the meaning of a keyword before the language spec is a... well I'll hold my tongue there.
If you do want your code modeled after spoken language you'd be best off writing in Objective-C, which incidentally also uses enums heavily for bitfields.