Typed enums

Typed enums were always a subject of confusion for me, since conversions to other integral types are implicit in some situations, and aren't implicit in others.

I want to have a typed enum, such that you can't set it to a non-enum type, however I don't know if this disallows other things. Perhaps someone can give me a solid answer for these scenarios:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum en_t // type en_t
{
  ZERO = 0,
  ONE,
  TWO,
  THREE
};


somearray[TWO] = foo;  // is this safe to do?  can it be assumed by
                       //  above that 'TWO' will == 2?
                       // Does the fact that it's typed prevent this?

if(TWO > ONE)  // what about this?
{
}


I didn't test the comparison bit, but the array thing works on my compiler, but is that reliable, expected, standard behavior?

Any light anyone can shed on the subject would greatly be appreciated.

Thanks
Yes, all existing implementations treat enums as, essentially, integer constants. C++ (and the upcoming C++0x) will expect more care when dealing with them, but they always promote to integer value, and are often used like that. (Note that in C++ they are their own type, so ONE + 1 is an error without an explicit cast to int...)

This is another hot topic among language junkies... but it is OK to do what you did above. Well, except for the names ZERO, ONE, TWO, etc. (Just use 0, 1, 2, etc directly, or BASE + 0, BASE + 1, etc) I presume you just ran off a list of foo for the example.


The whole purpose of an enumeration is so that the actual value is unimportant when looking from the outside-in. For example, some excerpts from my Blackjack game:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//----------------------------------------------------------------------------
// Card class
//   A card represents four things:
//    - an integer index in 0..52
//    - a suit in 0..13
//    - a face in Spades..Hearts
//    - an integer BlackJack game value
//   A suit/value of zero is the Joker (unused in this program)
//
const char* SuitNames[] =
  {
  "Joker", "Ace",   "Two",  "Three", "Four", "Five",  "Six",
  "Seven", "Eight", "Nine", "Ten",   "Jack", "Queen", "King"
  };

const int SuitValue[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10 };
enum Suit
  {
  Joker   =  0,
  Ace     =  1,
  Jack    = 11,
  Queen   = 12,
  King    = 13,
  SoftAce = 11
  };

const char* FaceNames[] = { "Spades", "Diamonds", "Clubs", "Hearts" };
enum        Face          {  Spades,   Diamonds,   Clubs,   Hearts  };

//............................................................................
struct Card
  {
  Face face;
  Suit suit;

  Card( int index = Joker ):
    face( (Face)( (index != Joker) ?  ((index -1) /13)     : Spades ) ),
    suit( (Suit)( (index != Joker) ? (((index -1) %13) +1) : Joker  ) )
    { }
  Card( int suit, int face ):
    face( (Face)face ),
    suit( (Suit)suit )
    { }
  inline int value()    const { return SuitValue[ suit ]; }
  inline operator int() const { return SuitValue[ suit ]; }
  inline bool ace()     const { return suit == Ace;       }
  inline int  index()   const { return (face *13) +suit;  }
  };

//............................................................................
ostream& operator << ( ostream& outs, const Card& card )
  {
  ostringstream ss;
  ss << SuitNames[ card.suit ];
  if (card.suit != Joker) ss << " of " << FaceNames[ card.face ];
  outs << ss.str();
  return outs;
  }

In this case, you can see that the enumerations are used in several different ways:
1. As constant names for specific values
2. As a name for a magic number (the Joker need not be zero)
3. As a convenience when reading the code --> understanding the meaning of the code

So, to create a card as the Ace of Spades, I don't have to remember anything special about how the cards are ordered. I can just say:
Card card( Ace, Spades );
(The other constructor is for if you don't care what the card is when you create it -- only that it is unique among 52:
1
2
for (unsigned n = 0; n < 52; n++)
  cout << Card( n );
prints the whole deck.)


Others may disagree about using enumerations this way, but that's how things stand. In other languages, like Pascal, the actual numeric value of an enumeration is specifically unknown to you (supposedly), but in C and C++ the value can be manipulated as a convenience to the implementor of the interface.

Hope this helps.
Last edited on
Yeah that does help. Thanks.

I'm wanting to do something similar. I want to have a string look-up for my enums just like you have for your suit names in the example, abut also want so have embedded "markers" so that I can do comparison checks. It's difficult to explain, but it boils down to something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum errors
{
ERROR_LEVEL_1,
ERROR_A,
ERROR_B,

ERROR_LEVEL_2,
ERROR_C,
ERROR_D,

ERROR_LEVEL_3,
ERROR_E,
ERROR_F
};

error e = someerror;

if(e > ERROR_LEVEL_3)
  OSht();


I suppose the alternative would be to have another LUT that has the 'level' of each error and use the enum to index that, instead of using the enum directly:

 
if( errorlevel_lut[e] > 3 )


But this would require me to create and maintain an entirely separate LUT and I'd have to make sure it stays in sync with my enum, which is no fun.
Topic archived. No new replies allowed.