A nifty little preprocessor trick for C++
We found out something a little surprising about C++ enums a while back. It turns out that if you have this code:
class Foo {
public:
enum Color {
Red = 1 << 0,
Green = 1 << 1,
Blue = 1 << 2
};
void Bar() {
Color purple = Color::Red | Color::Blue;
}
};
It turns out that this code is illegal for a few reasons. First of all, it turns out that you can’t refer to elements of a nested enum through the enum name. Instead you need to refer to them through the outer type’s name. i.e. instead of Color::Red, you’d say Foo::Red, Foo::Green or Foo::Blue. Secondly, while enums are implicitly convertible to integers, the reverse is not true. So applying the binary operator | to two enum values produces an int which then needs to be casted back to the enum type. So the code would actually have to look like:
void Bar() {
Color purple = (Color)(Red | Blue);
}
Or, if you’re external to the class it’ll look like
void Bar() {
Foo::Color purple = (Foo::Color)(Foo::Red | Foo::Blue);
}
Neither of which are particularly nice to use in practice. Casting means that even operators like |= are unpleasant to use. And referring to the values through the outer type name is just confusing. An alternative to solve the latter problem is to just make the enums top level. i.e. have something like:
enum Color {
Red = 1 << 0,
Green = 1 << 1,
Blue = 1 << 2
};
That makes things a little nicer now that you can refer to the elements trough the more natural Color::Red. However, we’re also now polluting the top level namespace with the enum member names. This means that I now can’t add an enum like:
enum StopLightState {
Red,
Yellow,
Green
};
because the “Red” value will conflict. So in our codebase we had these enums defined using ugly prefixes in order to not pollute the namespace (i.e. Color_Red, Color_Green, Color_Blue), and we would also just store those values into DWORDs so that we wouldn’t have to be casting on every operation.
Clearly this was pretty suboptimal. The naming is just redundant we didn’t like that code duplication (not to mention the ugliness of the prefixes), and by using DWORDs we were losing type safety. So what can we do about this? Well, thanks to a few C++ tricks it’s easily remediable. Instead of how I defined the enums as I did above instead try using the following pattern:
struct Color {
private:
Color() {}
public:
enum _Enum {
Red = 1 << 0,
Green = 1 << 1,
Blue = 1 << 2
};
};
typedef Color::_Enum ColorEnum
inline ColorEnum operator &(ColorEnum e1, ColorEnum e2) {
return (ColorEnum)(e1 & e2);
}
inline ColorEnum operator <<(ColorEnum e, int shift) {
return (ColorEnum)(e1 << shift);
}
//...define the rest of the operators that you’d like
Now I can write code just like this:
void Bar() {
ColorEnum purple = Color::Red | Color::Blue;
}
It’s now typesafe (no more DWORDs), and it allows convenient naming of the enum values without polluting the main namespace.
It’s turned out to be so useful that we now have macros to do the work for us. Specifically:
#define DECLARE_ENUM(name) \
struct name { \
private: \
name() {} \
public: \
enum _Enum {
#define END_ENUM(name) \
}; \
}; \
typedef name::_Enum name##Enum;
#define FLAGS_ENUM(name) \
inline name::_Enum operator &(name::_Enum e1, name::_Enum e2) { \
return (name::_Enum)(e1 & e2); \
} \
// … rest of the operators you care about go here.
Once you’ve done this you can now just write:
DECLARE_ENUM(Color)
Red = 1 << 0,
Green = 1 << 1,
Blue = 1 << 2
END_ENUM(Color)
FLAGS_ENUM(Color)
And now all of that boilerplate code will be written for you.
Useless for you? Quite possibly :) But very helpful for me.
Edit: I've added in the values that the enum members are initialized to so that they can be used effectively as named flags. However, it's somewhat annoying to force you to remember to write in (1 << n) for every member. Anyone have any ideas for how one might get that automatically?
Comments
Anonymous
February 01, 2005
This is a misuse of enums IMO. Why not just use constants (or even #defines) instead?Anonymous
February 01, 2005
Personally, I find that enumerated values shouldn't allow combinations that aren't also enumerated values. Being able to come up with an answer that's not in the enumeration seems extremely silly to me. It makes sense for flags, but flags aren't really enumerations (unless you want to list and name all the combinations for any particular family of flags, which you probably don't....). For your traffic light example, for instance, Red | Green is meaningless; the traffic light can't show that value, so it should be prohibited. Red | Amber, OTOH, should be a value within the enumeration, as it's a possible combination.
If you're going to allow non-enumerated values to appear you probably shouldn't be using an enumeration at all.Anonymous
February 01, 2005
And they say that MACROs in C# would be useless... imagine what you could do there too!Anonymous
February 01, 2005
Nick: This isn't a good case for macros in C# because C# enums are way nicer to work with than C++ enums.Anonymous
February 02, 2005
This still won't work unless you explicitly give each item in the enumeration a power-of-two value. In your example, Red | Blue is just going to be ( 0 | 2 ) = 2 = Blue.Anonymous
February 02, 2005
Pavel: Constants give me no type safety.
DrPizza: You're right. And that's why the FLAGS_ENUM portion of the macro is optional. If your enum is meaningless as a flag then you can leave it out.
Dan: You're correct. I was leaving that out for brevity, but i can see how confusing that made it and I'll add it back in.Anonymous
February 02, 2005
But what about the code generation Cyrus? Does it generate the same code as the "ugly" source? If it generates a load more code, its not worth it IMHO.Anonymous
February 02, 2005
Andy: You'd rather write out all the operators by hand? Right now i have
operator |
operator &
operator ~
operator <<
operator >>
operator |=
operator &=
operator <<=
operator >>=
This macro gives me C# like usage of enums. They're typesafe, and can be used as named flags handily.Anonymous
February 02, 2005
"DrPizza: You're right. And that's why the FLAGS_ENUM portion of the macro is optional. If your enum is meaningless as a flag then you can leave it out. "
But that still isn't correct, because it doesn't force combinations that are legal.
For a traffic light, for example, Red | Amber should be legal (as it's one of the states that a traffic light can have) and should have a member in the actual enumeration, but Red | Green should not (because it's not a state a traffic light can actually have).Anonymous
February 02, 2005
DrPizza: That's why StopLightState wasn't a flag enum. It was just there to show an example of namespace pollution.Anonymous
February 02, 2005
The StopLightState didn't use the macros at all, so who was to know one way or the other.
But the issues remains. Flags shouldn't be enums because the results of combining flags aren't enumerated values.Anonymous
February 02, 2005
I think this is great! I've got several places where this will really clean-up some code...Anonymous
February 02, 2005
Actually, this is a flawed usage of enums. as Stroustrup says: "An enumeration is a type that can hold a set of values specified by user". so, if you define an enum Color to hold values Red, Blue and Green, then the Purple is not a member of the Color, and declaring it as such is breaking type-safety. IMO, the closest match to flags semantics is a struct with constants defined in it.Anonymous
February 02, 2005
The comment has been removedAnonymous
February 03, 2005
Zdeslav: I really couldn't care less what stroustroup says :)
Also, in your example i lose typesafety when it's a constant inside a struct.
Oh, i love the enum definition which automatically increments the values. Very very cute :)Anonymous
February 03, 2005
it doesn't matter what stroustrup says, i agree, but that's also what C++ standard says. Now, i understand that adherence to standards is not always the top priority in MS, but they have they place (don't get me wrong, i'm not one of linux zealots, i use MS tools almost exclusivelly and i think they're great).
Besides, when you cast any number into an enum, you also lose type-safety. There's is something similar in COM: if you have a method which receives a parameter which is an enum, and you try to pass a value which is not defined in the enum, there is no guarantee that the call will be successfully marshaled, and there is a good reason for that. Enum does not mean any number, it means exactly what it says: an enumeration. If you just want to pass any integer to a function, use integers.
No offense, but for me, this is just a bad coding practice.Anonymous
February 03, 2005
sorry to be nittpicking here, but i would like to correct myself: this is not a bad coding practice, it's an error. C++ standard from 1998 (ISO/IEC14482) states this (5.2.9): "A value of integral type can be explicitly converted to an enumeration type. The value is unchanged if the integral value is within the range of the enumerated values (7.2) Otherwise, the resulting enumeration value is unspecified."
Therefore, combining Red and Blue values and casting it into Color, as in your example, results in undefined behavior.Anonymous
February 03, 2005
Here is another idea for assigning powers-of-2 to enum values:
#define LCONS(a) 1, a = 1 >> 0
#define CONS(a, b) PRIV_CONS(a, b)
#define TRIM(x) PRIV_TRIM(x)
#define PRIV_CONS(a, x, ...) (x+1), a = 1 >> x, VA_ARGS
#define PRIV_TRIM(x, ...) VA_ARGS
#define A b, c
#define P(b, c) c, b
enum X { TRIM(
CONS(RED,
CONS(ORANGE,
CONS(YELLOW,
CONS(GREEN,
CONS(BLUE,
LCONS(MAGENTA) ) ) ) ) )
)};
It's just a "cute" use of macros. i'm not sure if the idea can be tweaked enough to turn it into one usable in practice.Anonymous
February 03, 2005
Ooops.. the A and P function-like macros are just leftovers from where I have copied the code: it has nothing to do with the solution.Anonymous
February 05, 2005
The operators you've written are incomplete and will recurse to their doom. You need functions more like:
E& operator|= (E& a, E b) { return static_cast<E>(a+0|b); }
E operator| (E a, E b) { return a |= b; }
Your macro stunt breaks down if you want to provide a good implementation of operator ~ (unless you're willing to set bits that have no corresponding enumerator).
As others have pointed out, your example is quite questionable and using flags to represent traffic lights is not a very sensible thing to do. Shifting flags is something else to avoid under normal circumstances.
Don't worry about what Zdeslav said; if he had read the section referenced from the one he quoted (specifically 7.2 paragraphs 5 and 6) he'd understand that the enum can hold all the necessary values; even if it couldn't, the behaviour wouldn't be "undefined" - the sentence he quoted explains that it's unspecified, which is different (it can truncate, saturate or whatever, but not crash).
You should also be aware that names starting with an underscore and a capital letter are reserved for the implementation and shouldn't be used by diligent programmers: see section 17.4.3.1.2 in the C++ standard. That means your _Enum member should be renamed. In the C standard, names beginning with a capital E and an upper-case letter are also reserved as macro names for constants like EINVAL... do you remember the errno variable from <errno.h>? That would discount your END_ENUM macro, but I'm not sure whether the restriction applies to C++.
While you're looking clause 17, you might as well read section 17.3.2.1.2 (Bitmask types), because it describes exactly what you're doing here (sans macros).
Finally, can I point out that if you really want to pretend that C++ has scoping rules for enums that are similar to C#, you have some work to do. With the definition above, the following compiles:
Color c(c);
struct MoreColourful: Color {};
...etc. The proper tool for the job would be namespaces:
namespace Colour { enum T { Red, Green, Blue }; }
Thanks.Anonymous
February 05, 2005
I should have read that before I posted it...
E& operator|= (E& a, E b) { return static_cast<E>(a+0|b); }
should be
E& operator|= (E& a, E b) { return a = static_cast<E>(a+0|b); }Anonymous
February 06, 2005
The comment has been removedAnonymous
May 16, 2005
Last time i checked, c++ (yes, even MSVC++) had a preprocessor macro defined that increments automatically each time it is used. That means you could modify your code slightly to use it, something like:
1 << _COUNT
or whatever it is, dont have my C++ book handy. That would give you the ability to automatically increment it each time, no need to manually do it.Anonymous
February 10, 2006
How do you reset the _COUNT?Anonymous
June 08, 2009
PingBack from http://insomniacuresite.info/story.php?id=2173Anonymous
June 16, 2009
PingBack from http://lowcostcarinsurances.info/story.php?id=4692Anonymous
June 18, 2009
PingBack from http://barstoolsite.info/story.php?id=386