Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Controlling visibility of enum values

Consider a C++ class that exports an enum, maintains an internal array over that enum, and wants to export a command that accepts values from the enum.

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 the actual colors, but not NUM_COLORS? I do not want to have to check for the edge case on every call when the compiler's type system really ought to be able to do it for me.

The obvious hack is:

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, waiting for some poor overworked maintenance programmer to add provisions for blue LEDs, and forget to update the NUM_COLORS line.

Let me clarify a bit. What I want, in this particular case, is to be able to say:

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

It is my understanding that C++ doesn't allow this.

like image 698
John R. Strohm Avatar asked Apr 01 '11 16:04

John R. Strohm


Video Answer


2 Answers

Is putting the enumeration into a base class an option?

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 wouldn't do this, as it looks like an overly complex work around. I would simply forbid the caller to pass NUM_COLORS. True, the type system doesn't check that. But surely this is an easy thing to check for human programmers. Why would they pass NUM_COLORS?

like image 178
Johannes Schaub - litb Avatar answered Sep 17 '22 19:09

Johannes Schaub - litb


My first thought would be to try and solve the problem as you lay it, but after a bit reflexion, I would shift the burden to command:

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

Since enums are so weak types, you need to check the input anyway, as anyone could 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 going on (and I actually originaly typed green = impl::yellow; though it does not matter if you never refer to impl's values directly).

Otherwise, there is always the 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 obscure preprocessor machinery to avoid code duplicaton. It obviously only works for consecutive enum elements (it returns the number of elements, not the maximum number).

like image 31
Matthieu M. Avatar answered Sep 18 '22 19:09

Matthieu M.