I\'m a big fan of C++\'s strong-typing features and what I like the most is to use enumerations while dealing with limited sets of data.
But enumerations lack some u
As you've noticed, enum
in C++ is not an enumerated type,
but something more complex (or more mixed). When you define an
enum
, you define in fact two things:
An integral type with a legal range sufficient to contain an
or of all of the enumerated values. (Technically: the range
is 2^n - 1
, where n
is the number of bits necessary to
hold the largest value.)
A series of named constants having the newly defined type.
(I'm not sure what happens with regards to the range if you explicitly specify an underlying type.)
Given your enum Prime
, for example, the legal values would be
all integers in the range [0...64)
, even if all of these
values don't have a name. (At least if you didn't specifically
say that it should be an int
.)
It's possible to implement an iterator for enums without
initializers; I have a program which generates the necessary
code. But it works by maintaining the value in an integral type
which is large enough to contain the maximum value plus one. My
machine generated implementations of ++
on such an enum will
assert
if you try to increment beyond the end. (Note that
your first example would require iterating h
one beyond the
last value: my implementation of the various operators does not
allow this, which is why I use an iterator.)
As to why C++ supports the extended range: enum
are often used
to define bit masks:
enum X
{
a = 0x01,
b = 0x02,
c = 0x04,
all = a | b | c,
none = 0
};
X operator|( X lhs, X rhs )
{
return X((int)lhs | (int)rhs);
}
// similarly, &, |= and &=, and maybe ~
One could argue that this use would be better handled by
a class, but the use of enum
for it is ubiquitous.
(FWIW: my code generator will not generate the ++
, --
and
the iterators if any of the enum values has an explicitly
defined value, and will not generate |
, &
etc. unless all of
the values have explicitly defined values.)
As to why there is no error when you convert some value outside
the legal range (e.g. 100, for X
, above) is simply in keeping
with the general philosophy inherited from C: it's better to be
fast than to be correct. Doing extra range checking would
entail additional runtime cost.
Finally with regards to your last example: I don't see this as
a realistic use of enum
. The correct solution here is an
int[]
. While the C++ enum
is rather a mixed breed, I would
only use it as a real enumerated type, or for bit masks (and
only for bit masks because it is such a widely established
idiom).
You can use a switch
:
class Invalid {};
Prime& operator ++(Prime& p)
{
switch(p)
{
case n00: return n01;
case n01: return n02;
case n02: return n03;
case n03: return n04;
case n04: return n05;
case n05: return n06;
case n06: return n07;
case n07: return n08;
case n08: return n09;
case n09: return n10;
case n10: return n11;
case n11: return n12;
case n12: return n13;
case n13: return n14;
case n14: return n15;
// Here: 2 choices: loop or throw (which is the only way to signal an error here)
case n15: default: throw Invalid();
}
}
But note that this is not the right use of enums. I personally find this error-prone. If you want to enumerate integers, you can use an array of ints to do this, or for the case of prime numbers, a function (in mathematical sense: int nextPrime(int)
).