Controlling the visibility of enumeration values

Consider a C ++ class that exports an enumeration, maintains an internal array over this enumeration, and wants to export a command that takes values ​​from an enumeration.

class foo { public: enum color { red, yellow, green, NUM_COLORS }; private: something somebody[NUM_COLORS]; public: void command(color c); }; 

Is there a clean way to export only actual colors, but not NUM_COLORS? I do not want to check for an edge case on every call when a compiler-type system really needs to be able to do this for me.

Obvious hack:

 class foo { public: enum color { red, yellow, green }; private: /* something like */ const unsigned NUM_COLORS = green+1; unsigned LEDs_in_stock[NUM_COLORS]; public: void command(color c); }; 

This, of course, is a ticking time bomb, expecting some bad overloaded service programmer to add positions for the blue LEDs, and forget to update the NUM_COLORS line.

Let me explain a little. In this particular case, I want to say:

 class foo { public: enum color { red, yellow, green }; void command(color c); private: something somebody[color]; }; 

I understand that C ++ does not allow this.

+6
c ++ visibility arrays enums
source share
4 answers

My first thought was to try to solve the problem when you put it down, but after a little thought I would transfer the burden to command :

 void command(color c) { assert(0 <= c && c < NUM_COLORS && "Invalid argument"); } 

Since enums are so weak types, you still need to check the input, since anyone can easily provide crappy arguments:

 Foo foo; foo.command(static_cast<Foo::color>(3)); // 3 is green, right ? 

Original solution:

 class Foo { struct impl { enum { red, yellow, green, NUM_COLORS }; }; public: enum color { red = impl::red, yellow = impl::yellow, green = impl::green }; void command(color c); }; 

Unfortunately, there is a lot of duplication (and I really originally typed green = impl::yellow; although it doesn't matter if you never refer to impl values).

Otherwise, there is always a macro trick:

 #define MY_DEFINE_ENUM(Type, Elements) \ enum Type { BOOST_PP_SEQ_ENUM(Elements) }; \ inline size_t size(Type) { return BOOST_PP_SEQ_SIZE(Elements); } 

Which uses an evil macro and an obscure preprocessor mechanism to avoid code duplication. It obviously only works for consecutive enumeration elements (it returns the number of elements, not the maximum number).

+2
source share

Does an enumeration put a parameter in the base class?

 class foo_enums { public: enum color { red, yellow, green, NUM_COLORS }; protected: foo_enums() { } ~foo_enums() { } }; class foo : public foo_enums { private: unsigned LEDs_in_stock[NUM_COLORS]; /* make NUM_* values inaccessible */ using foo_enums::NUM_COLORS; public: void command(color c); }; 

Personally, I would not do this, because it seems like an overly complicated job. I would simply prohibit the caller from passing NUM_COLORS . True, the type system does not check this. But of course, this is easy to check for human programmers. Why should they pass NUM_COLORS ?

+3
source share

There is a fine line between protecting your future maintainers from simple and simple mistakes and trying to stop something that should be clearly wrong, for example, using the NUM_COLORS value.

In your case, I suggest approving the input in key functions and leaving it at the same time.

I believe that you can use the template proxy class, which specializes in static_assert and NUM_COLORS, so that users do not pass it to your functions.

I typed something that looked like work.

 class foo { public: enum color { red, yellow, green, NUM_COLORS }; class Color_Rep { color c; protected: Color_Rep(color which_color) : c(which_color) { } }; template <color C> struct Color : public Color_Rep { Color() : Color_Rep(C) { } enum { value = C }; }; private: int bar[NUM_COLORS]; public: void command(Color_Rep c); }; // Deny access to command(NUM_COLORS). template <> struct foo::Color<foo::NUM_COLORS> { }; int main() { foo().command(foo::Color<foo::red>()); foo().command(foo::Color<foo::green>()); foo().command(foo::Color<foo::NUM_COLORS>()); // Won't compile. } 
+1
source share

The solution is to use the card:

 std::map<color, unsigned> LEDs_in_stock; LEDs_in_stock[red] += 2; LEDs_in_stock[red]; // = 2 LEDs_in_stock[green]; // = 0 

This way you keep a clean enumeration and don't need hard coding of any size.

0
source share

All Articles