C++11 furnishes an improved enum
with enum struct
.
But this still suffers from what is surely - until you get inured to it
- the most surprising pitfall of the inferior old enum
: that the value of a
variable of type enum struct E
need not be any of the enumerated ones but
may be any value in the range of E
's underlying integral type:
enum struct X { one = 1};
X x = X(); // Fine! The value of x is int(0)
x = X(2); // Cool! The value of x is int(2)
Everywhere you use an object of enum
or enum struct
type E
, you
have to catch the case in which it "isn't one of the E
s".
How might one define a generic type that would be serviceable in
lieu of enum struct
(not necessarily a drop-in substitute)
with the property that an object of an instantiating class cannot
assume values other than the "enumerated" ones?
I mean cannot in a sense that is satisfied if the object will throw rather
than assume any undistinguished value (i.e. it catches the case
it which it would become "not one of the E
s").
And I say "enumerated" in scare-quotes because it seems unavoidable
(without resort to macros) that these values would be "enumerated"
by a sequence of integral type template parameters and could not be
accessed so conveniently as X::one
.
If that is unavoidable it is fine, as long as the "enumerated" values
become constants statically retrievable by enumeration index from the type.
(It would then be simple for client code to associate meangingful symbols with either
the indices or with the indexed values, and to encapsulate such a convenient
mapping - e.g. in a struct
-nested anonymous enum
!)
Is there already a well-regarded solution to this question that I don't know about?
Continued by commmentator request (Ali)
Could you post some pseudo-code? It should show how you would like to use it.
Here are some indications of the desired usage (I think):
/* 1 */
/* These 2 structs could just be tag-types X{}, Y{}
serving to distinguish value-safe-enums that
would otherwise be the same. But you could
also get more mileage out them, as shown...
*/
struct turn;
struct hand;
using vs_turn = val_safe_enum<turn,int,1,2>;
using vs_hand = val_safe_enum<hand,int,1,2>;
struct turn
{
// Want an anonymous scoped enum; only rvalue
enum {
right = vs_turn::which<0>(), // = 1
left = vs_turn::which<1>() // = 2
};
};
struct hand
{
enum {
right = vs_hand::which<0>(), //= 1
left = vs_hand::which<1>() // = 2
};
};
/* End 1 */
/* 2 */
char const * foo(vs_turn const & t) {
// Only 2 possibilities!
return int(t) == turn::right ? "right turn" : "left turn";
}
char const * foo(vs_hand const & h) {
return int(h) == hand::right ? "right hand" : "left hand";
}
/* End 2 */
vs_turn t1(0); // 3.a OK
vs_turn t2(turn::right); // 3b. OK
vs_hand h1(hand::left); // 3c. OK
vs_hand h2(1); // 3d. OK
t1 == vs_turn(turn::right); // 4. OK
t1 < t2; // 5. OK
t1 = t2; // 6. OK
int(t1) == turn::right; // 7. OK. Explicit conversion to underlying type.
/* 8 */
switch(int(t1)) {
case turn::right:
/* Something */ break;
case turn::left:
/* Something */;
// No default necessary!
}
/* End 8 */
vs_turn t3(3); // 9. Throw! Value out of range
vs_turn t4; // 10. Error. No default construction.
t1 == turn::right; // 11a. Error. No Conversion
t1 <= h1; // 11b. Error. No conversion.
t1 = turn::right; // 11c. Error. No conversion
t1 = h1; // 11d. Error. No conversion.
foo(turn::right); // 11e. Error. No conversion