Enumerated type and RNG

M

mike3

Hi.

How could I resolve this C++ programming dilemma?

---
enum smth { ZERO, ONE, TWO, MAX }; // not really labeled this way

void f(smth parameter)
{
( ... )
}

( ... )
f(static_cast<smth>(RNG(MAX))); // RNG generates number from 0 to
MAX-1
( ... )
f(TWO); // f is called with fixed constant
( ... )
f(666); // won't work -- good
( ... )
---

Note the marked bit. f() takes the enumerated type smth, so you can't
just stick "666" in there and cause something bad that way, yet I
also need to pass a randomly obtained value from that list, which
requires a cast. Is it considered good practice to use a cast in this
situation? Would it be better to have f() take an "int" or something
like
that and then include an explicit check in there to see if the value
is
outside 0...MAX-1?

Finally, I suppose one could also do this:
int rand(RNG(MAX));
if(rand == ZERO) f(ZERO);
if(rand == ONE) f(ONE);
if(rand == TWO) f(TWO);

This avoids the cast, but seems to introduce a mantainability defect
and
making the code more susceptible to bugs: if one goes and adds more
values to the enum, then must be added here, too (not just in f()),
and
if this is forgotten about... bug!

What should be done here?
 
J

James Kanze

How could I resolve this C++ programming dilemma?
void f(smth parameter)
{
( ... )
}
( ... )
f(static_cast<smth>(RNG(MAX))); // RNG generates number from 0 to
MAX-1
( ... )
f(TWO); // f is called with fixed constant
( ... )
f(666); // won't work -- good
( ... )
---
Note the marked bit. f() takes the enumerated type smth, so
you can't just stick "666" in there and cause something bad
that way, yet I also need to pass a randomly obtained value
from that list, which requires a cast. Is it considered good
practice to use a cast in this situation? Would it be better
to have f() take an "int" or something like that and then
include an explicit check in there to see if the value is
outside 0...MAX-1?

No, however...
Finally, I suppose one could also do this:
int rand(RNG(MAX));
if(rand == ZERO) f(ZERO);
if(rand == ONE) f(ONE);
if(rand == TWO) f(TWO);
This avoids the cast, but seems to introduce a mantainability
defect and making the code more susceptible to bugs: if one
goes and adds more values to the enum, then must be added
here, too (not just in f()), and if this is forgotten about...
bug!
What should be done here?

Formally, I guess you probably should use some sort of mapping
array (indexed by the return value of rand), but in practice,
I'd just cast. What I would do, however, is isolate it in a
function, e.g.:

smth
rand( smth max )
{
return static_cast< smth >( rand( +max ); }
// Note the unary +, to force max to an integral
// type and avoid infinite recursion.
}

This way, you don't have casts all over the place.
 
M

mike3

No, however...


Formally, I guess you probably should use some sort of mapping
array (indexed by the return value of rand), but in practice,
I'd just cast. What I would do, however, is isolate it in a
function, e.g.:

smth
rand( smth max )
{
return static_cast< smth >( rand( +max ); }
// Note the unary +, to force max to an integral
// type and avoid infinite recursion.
}

This way, you don't have casts all over the place.

So does this mean that for every enum one wants to use the RNG
to generate a value for, one would need tiny "rand" functions
(inline, presumably)? Where would one put all that stuff?
 
M

mike3

So does this mean that for every enum one wants to use the RNG
to generate a value for, one would need tiny "rand" functions
(inline, presumably)? Where would one put all that stuff?

Could one use a template? Like this?:

---
template<class E>
inline E rand(E max)
{
return(static_cast<E>(rand(+max)));
}
 
J

James Kanze

So does this mean that for every enum one wants to use the RNG
to generate a value for, one would need tiny "rand" functions
(inline, presumably)?

And how many is that? Formally, enum types don't support random
generation in C++, so it's up to you to implement it if you need
it. Just as you do for most other stuff concerning enums
(iterators, | and & operators, etc.).
Where would one put all that stuff?

Where ever you put the implementation of any other functions
associated with the enum.

Enums, in C++, are a bit funny. They're designed to fulfill at
least two needs, and so do neither completely, since the
operations required to fulfil one of the needs don't make sense
when the enum is used for the other need. (Increment doesn't
make sense when the enum is used to define bit masks, and & and
| don't make sense when the enum is used to define an
enumeration.)
 
J

James Kanze

On Jan 2, 2:27 pm, mike3 <[email protected]> wrote:

[...]
Could one use a template? Like this?:
---
template<class E>
inline E rand(E max)
{
return(static_cast<E>(rand(+max)));
}
---

Probably, but I'd worry about ambiguities when calling rand,
potentially resulting in infinite recursion. Maybe if you put
it in a special namespace, however.

What we'd really like, of course, is some means of automatically
getting the maximum value, so that you could write:
SomeEnum e = rand< SomeEnum >();
(This can be done if you explicitely instantiate
std::numeric_limits for each enum type. Very elegant solution,
but probably a case of the cure being worse than the disease;
it's a lot of code to write.)
Then we need only one definition.

Again, how often does the case occur?
 
M

mike3

[...]

Could one use a template? Like this?:
---
template<class E>
inline E rand(E max)
{
return(static_cast<E>(rand(+max)));
}
---

Probably, but I'd worry about ambiguities when calling rand,
potentially resulting in infinite recursion. Maybe if you put
it in a special namespace, however.

Couldn't one also just call the RNG function something else,
and then go through this to access it for different types?
What we'd really like, of course, is some means of automatically
getting the maximum value, so that you could write:
SomeEnum e = rand< SomeEnum >();
(This can be done if you explicitely instantiate
std::numeric_limits for each enum type. Very elegant solution,
but probably a case of the cure being worse than the disease;
it's a lot of code to write.)

I suppose. It's easier to just have a "max" at the end of the enums.
Again, how often does the case occur?

Not sure what the final number will be, but it'd be more than just
one or two.
 
J

James Kanze

On Jan 2, 2:27 pm, mike3 <[email protected]> wrote:
[...]
Could one use a template? Like this?:
---
template<class E>
inline E rand(E max)
{
return(static_cast<E>(rand(+max)));
}
---
Probably, but I'd worry about ambiguities when calling rand,
potentially resulting in infinite recursion. Maybe if you
put it in a special namespace, however.
Couldn't one also just call the RNG function something else,
and then go through this to access it for different types?

Certainly. One could also define a template class, instantiated
on the enum type, and call a member function of it. There are
any number of solutions. (I would, in fact, use the template
class solution. But mainly because my random number generator
is already a class, designed for derivation to support such
things.)
I suppose. It's easier to just have a "max" at the end of the
enums.

Yes, but the problem is accessing it from a template. Enums
don't (at present) introduce a new scope, so two templates in
the same scope can't both define an element max. And if the
name is different (to reflect the enum type), then you're
screwed with regards to templates.
 
M

mike3

On Jan 4, 12:59 am, mike3 <[email protected]> wrote:

Certainly.  One could also define a template class, instantiated
on the enum type, and call a member function of it.  There are
any number of solutions.  (I would, in fact, use the template
class solution.  But mainly because my random number generator
is already a class, designed for derivation to support such
things.)

You mean like make the RNG bit a class itself, that you then declare
variables as a type of? But then you still have the problem of
getting the right max.
Yes, but the problem is accessing it from a template. Enums
don't (at present) introduce a new scope, so two templates in
the same scope can't both define an element max. And if the
name is different (to reflect the enum type), then you're
screwed with regards to templates.

How does the using different names "screw it" with regards to
templates? You mean because one has to sort of "specify the type
twice", once in the template, the next in the max? How can one
avoid this? How do you do it with your RNG?
 
J

James Kanze

You mean like make the RNG bit a class itself, that you then
declare variables as a type of? But then you still have the
problem of getting the right max.

Yes. My idea was to have a template class calling rand() (or
some other generator), with the max value a template argument.
How does the using different names "screw it" with regards to
templates? You mean because one has to sort of "specify the type
twice", once in the template, the next in the max?

More or less. The template has to know the type name, in order
to declare the return type, and it needs to know the maximum
value. If enums created a scope, you could simply require that
the enum define a reserved entry, max, and the maximum value, in
the template, would be T::max. Because enum's don't introduce
scope, however, this would mean that you cannot have more than
one such enum in each namespace. And if the name is T_max, or
some such (with the name of the type embedded), you can't access
it in the template.

The classical solution for this is a traits template class, the
standard even provides one which you can specialize
(std::numeric_limits), but it requires defining a lot more than
you need here.
How can one avoid this? How do you do it with your RNG?

I don't. I've never actually needed a random enum, so I'm just
speculating on solutions. If I did need one... I already have
code which can parse enums (originally in order to generate a
mapping between strings and the enum values); it would be
trivial to add support for rand to it, and generate the required
classes or whatever automatically. (Note that there can be some
tricky aspects. When I added generation for iterators, I needed
a type which would hold "one past the end". Since I only
support iterators for enums with no user defined values, I just
used "unsigned"---it seems a reasonable restriction that my code
won't support an enum with more than UINT_MAX-1 members. I
suspect that a similar constraint would be acceptable in your
case.)
 
T

Thomas J. Gritzan

Am 09.01.2010 14:25, schrieb James Kanze:
More or less. The template has to know the type name, in order
to declare the return type, and it needs to know the maximum
value. If enums created a scope, you could simply require that
the enum define a reserved entry, max, and the maximum value, in
the template, would be T::max. Because enum's don't introduce
scope, however, this would mean that you cannot have more than
one such enum in each namespace.

Easiest way would be an enum in a struct. For example:

/// Don't forget to document this mysterious function
template <typename Enum>
typename Enum::type getEnumMax() { return Enum::max; }

struct Enum1 {
enum type {
value1,
value2,
max
};
};

In C++0x you can use "enum class" instead.
 
J

James Kanze

Fortunately, C++0x will have scoped enums:
enum class scoped_enum { a, b, c };
enum class another_scoped_enum { b, c, a };
scoped_enum::a and another_scoped_enum::a are distinct names.

Yes, but if I tell my boss that we can't deliver until Visual
C++ supports scoped_enum, I don't think it will go over too
well.
 
M

mike3

On Jan 9, 9:51 am, mike3 <[email protected]> wrote:
More or less.  The template has to know the type name, in order
to declare the return type, and it needs to know the maximum
value.  If enums created a scope, you could simply require that
the enum define a reserved entry, max, and the maximum value, in
the template, would be T::max.  Because enum's don't introduce
scope, however, this would mean that you cannot have more than
one such enum in each namespace.  And if the name is T_max, or
some such (with the name of the type embedded), you can't access
it in the template.

The classical solution for this is a traits template class, the
standard even provides one which you can specialize
(std::numeric_limits), but it requires defining a lot more than
you need here.

Hmm. So then what should I do?
I don't.  I've never actually needed a random enum, so I'm just
speculating on solutions.  If I did need one... I already have
code which can parse enums (originally in order to generate a
mapping between strings and the enum values); it would be
trivial to add support for rand to it, and generate the required
classes or whatever automatically.  (Note that there can be some
tricky aspects.  When I added generation for iterators, I needed
a type which would hold "one past the end".  Since I only
support iterators for enums with no user defined values, I just
used "unsigned"---it seems a reasonable restriction that my code
won't support an enum with more than UINT_MAX-1 members.  I
suspect that a similar constraint would be acceptable in your
case.)

What does this "parse enums" thing do and how does it work?
 
J

James Kanze

Hmm. So then what should I do?

Whatever seems best to you. There is no perfect solution. I've
presented some of the alternatives, but it's up to you to choose
which one seems best for you.
What does this "parse enums" thing do and how does it work?

Originally, it was just a bit of lex and a very simple state
machine, to determine when an enum started, extract its name and
members, and then write out the necessary code for mapping
between the enum values and strings. It then got extended to
handle namespaces and classes, so that it could be used to map
nested enums, and to generate other things as well; once you've
collected the information, there are a lot of things you can
generate easily.

Another alternative is to invent a little language to describe
your enums, and generate the enum declaration and any additional
code you want from that. That's easier even than parsing; it
tends to work best when the enums are in global scope (but you
can add options for it to generate e.g. namespaces, and if you
prefer your enums in classes, you can do that as well). For the
simplest case, you can even use a small language, like AWK, for
the generator. (This is doubtlessly the route I would have
taken, rather than my parser, if I hadn't had to deal with
legacy code which used enum, and not any little language I could
invent. And the fact that my enums were often members of a
larger class.)
 
M

mike3

Whatever seems best to you.  There is no perfect solution.  I've
presented some of the alternatives, but it's up to you to choose
which one seems best for you.

I suppose.
Originally, it was just a bit of lex and a very simple state
machine, to determine when an enum started, extract its name and
members, and then write out the necessary code for mapping
between the enum values and strings.  It then got extended to
handle namespaces and classes, so that it could be used to map
nested enums, and to generate other things as well; once you've
collected the information, there are a lot of things you can
generate easily.

Another alternative is to invent a little language to describe
your enums, and generate the enum declaration and any additional
code you want from that.  That's easier even than parsing; it
tends to work best when the enums are in global scope (but you
can add options for it to generate e.g. namespaces, and if you
prefer your enums in classes, you can do that as well).  For the
simplest case, you can even use a small language, like AWK, for
the generator.  (This is doubtlessly the route I would have
taken, rather than my parser, if I hadn't had to deal with
legacy code which used enum, and not any little language I could
invent.  And the fact that my enums were often members of a
larger class.)

Mmm. This seems to be highly complicated, far too much for what
is otherwise a simple problem, even simpler than converting to
strings.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top