smarter enums

M

Mark A. Gibbs

i have been toying with the idea of making my enums smarter - ie, more
in line with the rest of the language. i haven't tested it yet, but what
i came up with is a template like this:

template <typename Enum>
class smart_enum
{
public:
typedef Enum enum_type;

smart_enum() {}
smart_enum(enum_type e) e_(e) {}

enum_type get() const { return e_; }

bool operator==(smart_enum const& e) const { return e.e_ == e_; }
bool operator!=(smart_enum const& e) const { return !(e_ == *this); }

operator enum_type() const { return e_; }

private:
enum_type e_;
};

i would like this to be used to transparently replace existing enum
types - with one exception. i want to disallow:

colour c = green;

and force:

colour c = colour::green;

otherwise, i want the template to behave exactly like a regular enum
type. would this template work? or am i missing anything?

the next thing i am thinking about is making the transition to smart
enums easy, using macros. something like this:

#define DEFINE_ENUM_START(enum_t) \
namespace enum_t ## _DUMB_ENUM_ { \
typedef enum
#define DEFINE_ENUM_END(enum_t) \
enum_t; } \
typedef smart_enum< enum_t ## _DUMB_ENUM_ :: enum_t > enum_t;

again, not tested or even compiled. but what i'd like to be able to do is:

DEFINE_ENUM_START(colour)
{
red,
green,
blue
}
DEFINE_ENUM_END(colour)

has anyone seen anything like this before? are there any problems with
doing this?

mark
 
V

Victor Bazarov

Mark A. Gibbs said:
i have been toying with the idea of making my enums smarter - ie, more
in line with the rest of the language. i haven't tested it yet, but what
i came up with is a template like this:

template <typename Enum>
class smart_enum
{
public:
typedef Enum enum_type;

smart_enum() {}
smart_enum(enum_type e) e_(e) {}

enum_type get() const { return e_; }

bool operator==(smart_enum const& e) const { return e.e_ == e_; }
bool operator!=(smart_enum const& e) const { return !(e_ == *this); }

operator enum_type() const { return e_; }

private:
enum_type e_;
};

i would like this to be used to transparently replace existing enum
types - with one exception. i want to disallow:

colour c = green;

and force:

colour c = colour::green;

otherwise, i want the template to behave exactly like a regular enum
type. would this template work? or am i missing anything?

the next thing i am thinking about is making the transition to smart
enums easy, using macros. something like this:

#define DEFINE_ENUM_START(enum_t) \
namespace enum_t ## _DUMB_ENUM_ { \
typedef enum
#define DEFINE_ENUM_END(enum_t) \
enum_t; } \
typedef smart_enum< enum_t ## _DUMB_ENUM_ :: enum_t > enum_t;

again, not tested or even compiled. but what i'd like to be able to do is:

DEFINE_ENUM_START(colour)
{
red,
green,
blue
}
DEFINE_ENUM_END(colour)

has anyone seen anything like this before? are there any problems with
doing this?

How are your "smart enums" going to work in a "switch" statement?

I am still trying to understand what advantage your "enum" has over
the existing enum mechanism? What exactly do you mean by "more in
line with the rest of the language"? Could you please demonstrate
for dummies like me?

V

P.S. Sorry for the bad quoted/added ratio, just wanted to keep all
of the original post
 
M

Mark A. Gibbs

Victor said:
How are your "smart enums" going to work in a "switch" statement?

i don't know, that was one of those things i hadn't thought of. wouldn't
the implicit conversion operator do the trick?
I am still trying to understand what advantage your "enum" has over
the existing enum mechanism? What exactly do you mean by "more in
line with the rest of the language"? Could you please demonstrate
for dummies like me?

mostly it prevents the enum member names from being injected into the
surrounding scope. but there's a lot of functionality you could add,
such as a default value, or maybe doing some more work while comparing,
assigning, or whatever.

just off the top of my head, consider the colour enumeration:

namespace colour_ENUM_ {
typedef enum
{
red,
green,
blue
} colour;
}

template <>
class smart_enum<colour_ENUM_::colour>
{
public:
typedef Enum colour_ENUM_::colour;

smart_enum() {}
smart_enum(enum_type e) e_(e) {}

enum_type get() const { return e_; }

bool operator==(smart_enum const& e) const { return e.e_ == e_; }
bool operator!=(smart_enum const& e) const { return !(e_ == *this); }

operator enum_type() const { return e_; }

// additional stuff
string get_colour_name() const {
const char* str;

switch(e_)
{
case red: str = "red";
case green: str = "green";
case blue: str = "blue";
}

return string(str);
}

long get_html_colour() const {
switch(e_)
{
case red: return 0xFF0000L;
case green: return 0x00FF00L;
case blue: return 0x0000FFL;
}
}

private:
enum_type e_;
};

existing code that used the old colour enum should work unchanged
(except that the scope must be added to the constants), but new code
could use the new members to get the colour in a format more useful for
output.

there are probably other things too - i've been musing over this for a
couple days now, but right now i have to run. if you can hang on for
about 3 days, i can probably get back to you with more. otherwise, there
was an article that i re-read recently (which i don't have with me and
isn't online) in c/c++ user's journal - one of the ones by hyslop &
sutter. april 2004, maybe? if not, probably may. that was what got me to
thinking on these lines. there might be more in there than i can come up
with.

but a good summary is that this is about maintaining source
backwards-compatibility (and to some degree, the syntax of enums), while
elevating enum types to first-class c++ types.

mark

p.s. you know, calling yourself a dummy is a sign of poor self-esteem,
but aside from that, if you project a self-image of being a dummy,
people will hardly go out of their way to dissuade you of your opinion,
and may end up agreeing by default. i'm not really a psychiatrist, but i
just saw one on tv.
 
V

Victor Bazarov

Mark A. Gibbs said:
Victor said:
How are your "smart enums" going to work in a "switch" statement?

i don't know, that was one of those things i hadn't thought of. wouldn't
the implicit conversion operator do the trick?
I am still trying to understand what advantage your "enum" has over
the existing enum mechanism? What exactly do you mean by "more in
line with the rest of the language"? Could you please demonstrate
for dummies like me?

mostly it prevents the enum member names [...]

Thank you for your explanation and your psychology (and not psychiatry)
lesson (do you know the difference? I probably don't, I only pretend
I do). However, I just wanted to point out that you may have started
with a wrong premise. Enums don't have members.

Best of luck!

Victor
 
A

Aguilar, James

Mark A. Gibbs said:

I don't have any advice to give you about how to do this, since I am just
learning the language myself. However . . .

I don't think that what you're trying to do is worth your time. My
understanding is that you are trying to design an enum that has some minor
added features over a standard enum. Basically, it's an enum that also
gives you the ability to define class functionality, such as methods and
operators.

But, as I understand it, the whole point of enums is that they aren't
classes, but just ints masked by constant names. They are supposed to be
quick and simple, with no bells and whistles. With C++, they also have
types, but those are simply to guarantee that the user knows what's going
on. AFAIK, you can still throw them around like the ints they represent,
such as:

enum Result { SUCCESS, FAILURE };

int main()
{
...
if (!error()) return SUCCESS;
else return FAILURE;
}

So my advice, which you might take, even though I'm as new to C++ as the
next guy, is to define classes if you need methods, and let enums be enums.
 
R

Robbie Hatley

Victor Bazarov asked Mark Gibbs about his new enums:

and Mark replied:
mostly it prevents the enum member names from being injected into the
surrounding scope.

Just use a namespace.
but there's a lot of functionality you could add,
such as a default value

Enums already have that.
or maybe doing some more work while comparing,
assigning, or whatever.

Seems to me, all these things can be done with existing
namespaces and enums. For example:

#include <iostream>

namespace Romulan
{
enum Ale
{
Brown = 15,
Amber = 16,
Lite = 17
};
}

int main()
{
int YourBeer;
std::cout << "Enter beer code:" << std::endl;
std::cin >> YourBeer;
switch (YourBeer)
{
case Romulan::Brown:
std::cout << "Yummy!" << std::endl;
break;
case Romulan::Amber:
std::cout << "Pretty good." << std::endl;
break;
case Romulan::Lite:
std::cout << "Eh." << std::endl;
break;
default:
std::cout << "Your beer sux, dude!" << std::endl;
break;
}
return 0;
}

All done without any "modified" enums.

--
Cheers,
Robbie Hatley
Tustin, CA, USA
email: lonewolfintj at pacbell dot net
web: home dot pacbell dot net slant earnur slant
 
J

JKop

Here's some code on working on at the moment:



enum EnumMonth
{
Jan = 1,
Feb = 2,
Mar = 3,
Apr = 4,
May = 5,
Jun = 6,
Jul = 7,
Aug = 8,
Sep = 9,
Oct = 10,
Nov = 11,
Dec = 12
};


class Month
{
private:

EnumMonth data;

void Set(EnumMonth month)
{
if (month < 1 || month > 12) throw bad_month();

data = month;
}

public:

class bad_month {};

explicit Month(EnumMonth month)
{
Set(month);
}

Month& operator=(Month month)
{
Set(month);
}

operator EnumMonth()
{
return data;
}
};


int main()
{
Month blah(EnumMonth(Apr));

Month blah(EnumMonth(4));

try
{
Month blah(EnumMonth(13)); //bad_month
}
catch (bad_month)
{

}

}


-JKop
 
T

Thomas Matthews

Mark said:
i have been toying with the idea of making my enums smarter - ie, more
in line with the rest of the language. i haven't tested it yet, but what
i came up with is a template like this:

This has been done before.
Use your favorite search engine and search for
"Dan Saks enumeration". You could also supplement
your search with "Scott Meyers enumeration"
and "Jim Hyslop enumeration". These folks have
worked out the conversion of an enumeration into
a type.

In today's world, your best effort should be spent
researching before designing. If its been done
before, either use it, tailor it, or improve it.

--
Thomas Matthews

C++ newsgroup welcome message:
http://www.slack.net/~shiva/welcome.txt
C++ Faq: http://www.parashift.com/c++-faq-lite
C Faq: http://www.eskimo.com/~scs/c-faq/top.html
alt.comp.lang.learn.c-c++ faq:
http://www.raos.demon.uk/acllc-c++/faq.html
Other sites:
http://www.josuttis.com -- C++ STL Library book
 
A

Arne Adams

Thomas Matthews said:
This has been done before.
Use your favorite search engine and search for
"Dan Saks enumeration". You could also supplement
your search with "Scott Meyers enumeration"
and "Jim Hyslop enumeration". These folks have
worked out the conversion of an enumeration into
a type.
I did a google on each of these but it seems that none of these addreses:
- getting a string representation of an enum value (and being able to convert
such a representation back to the enum value
- which is the first defined value in an enum
- which is the last defined value in an enum
- a typesafe operator++ which ideally (at least for some uses :) would
respect enums with gaps:

Example for the last one:
enum E { First = 0, Second = -23, Third = 1000};

I would want to be able to do this:

for(E value = begin<E>(); value != end<E>();++value)
....

and expect the loop to iterate over the values 0,-23,1000 in that order.

(Of course you can define such an enum with macros and templates)
 
V

Victor Bazarov

Arne said:
I did a google on each of these but it seems that none of these addreses:
- getting a string representation of an enum value (and being able to convert
such a representation back to the enum value

This is entirely unnecessary functionality from the programming
standpoint. None of my classes have that, why should I worry
about some enums (or "smart enums")? If there is a [rare] need
to do that, it could be programmed in a separate function, on
a case-by-case basis. Besides, what if I don't want to enter
"red" for colour::red, but "rouge" or "ËÒÁÓÎÏÅ"?

I guess, if you need to have a special _type_ like other folks
have done, you can always derive from their type and extend their
functionality to accommodate serialising, can't you?
- which is the first defined value in an enum

How is that relevant for programming, really? Enums are just
named constants. They usually don't have any order defined for
them. They are also not dependent.
- which is the last defined value in an enum

Same question of relevancy.
- a typesafe operator++ which ideally (at least for some uses :) would
respect enums with gaps:

Example for the last one:
enum E { First = 0, Second = -23, Third = 1000};

I would want to be able to do this:

for(E value = begin<E>(); value != end<E>();++value)
...

and expect the loop to iterate over the values 0,-23,1000 in that order.

Why? IIRC, in my more than 10 years of programming C++ I've _never_
had a need in ++ operator for enums, nor for "begin" and "end". If
you feel you need that functionality, then why not have a standard
list<int> with fixed values or even a simple array?

I think you're trying to create your own, user-defined, type with
some functionality that enum has never been known to have. Why do
you want to tie it up with the term enum or make them related in some
way? Wouldn't it be better to just say that you're creating a different
type altogether?

V
 
A

Arne Adams

Victor Bazarov said:
This is entirely unnecessary functionality from the programming
standpoint.
enums are a bit more than named constants.
For instance they facilitate debugging because (at least most debuggers I have
used) display the symbolic name of the enum value at hand.
Hence
enum Color
{red,blue,yellow};

is different from
namespace Color
{
const int red = 1;
const int blue = 2;
const int yellow = 3;
}

If you need to make these constants persistent some may find it useful to save
the symbolic names.

Apart from that each enum is a different type, it can be used for static
overloading and mismatchs are detected at compiletime.
Why? IIRC, in my more than 10 years of programming C++ I've _never_
had a need in ++ operator for enums, nor for "begin" and "end". If
you feel you need that functionality, then why not have a standard
list<int> with fixed values or even a simple array?

If I use enums to encode variations of a problem domain (for the typesafety, for
ease of debugging, for whatever reason) then it can be useful to be able to loop
over all values.

But of course it is possible that I am the only C++ programmer who needs this
sometimes.
 
T

Thomas Matthews

Arne said:
If I use enums to encode variations of a problem domain (for the typesafety, for
ease of debugging, for whatever reason) then it can be useful to be able to loop
over all values.

But of course it is possible that I am the only C++ programmer who needs this
sometimes.

Perhaps there is some blend between enum constants and enum ranges.
In Pascal, one can define a range of constants. Which is a shorthand
notation for a set of constants.

Are enums a set or just "random" constants with names. Let us discuss
this.

The enum facility allows declaring of values to named constants. The
compiler generates the values if the values are not specified. There
is no mandate that the values or names be related:
enum Fried_Tomato {Green, Chocolate, Torque, Velocity};
enum Punkin {Red = 400, Blue = 25, Speed = 66, Linguicity = 75};
The compiler is assigning names to constants and associating them
with a type.

However, this does parallel set notation. Not knowing the
specifics of enum, one expects that the identifiers are in a
set with the given tag identifier. If one views this as a
set, one expects to use set operations on the values,
especially iteration. But they are not a set, as one cannot
insert into the set. Maybe a constant set? Nope, as a
set implies unique values to the names.

More confusion: One can declare a variable of the enumeration
type and perform integral operations on that variable.
Given:
Fried_Tomato automobile(Torque);
One could issue this statement:
cout << automobile << '\n';
and have the value of 3 printed out.
Taking this one more step:
automobile += 7;
cout << automobile << '\n';
will print out the value of 10.
Hmmm, enums are not just constants or are they?

The enum type identifier when used to declare a object
will declare an integer value. So the statement:
Punkin james;
is the same as
int james;
This explains the above behavior of automobile.

The above behavior also shows that an enum is not a set.
One cannot increment a set nor perform addition with the
set, as the set is not one value but a collection. Addition
of an integer with a set is not defined in C++. Perhaps
in the relm of mathematics (such as scalar addition with
a vector).

One can use a named constant from an enum as the constant
in a 'case' statement or as the size of an array. A member
of a set cannot be used this way.

Both set and enum do not provide facilities for converting
from the value to the named constant. Many people have
wanted this functionality (I know that it really helps
in debugging).

Let us extend this confusion by comparing an enum to a
range. First a definition of a range {mine}:
A range is a contiguous, sequence of integers
from a beginning value to an ending value, inclusive,
within the capacity of the int type.
So many programmers often use the enum to represent a
range of values:
enum Weekday {Sunday, Monday, Tuesday, Wednesday,
Thursday, Friday, Saturday};
The range is Sunday .. Saturday; values 0 .. 7. The
range, because it is contiguous, allows incrementing
and decrementing. However, extending beyond the
beginning or end is undefined. Thus one could have
these statements:
Weekday today(Sunday);
Weekday tomorrow;
tomorrow = today + 1;
if (tomorrow == Monday)
{
// This code would always be executed.
}
Enums, as shown above, exhibit the same behavior.
related."

Yes, this is where many programs crash. I could make
one subtle change:
enum Weekday {Sunday, Monday = 15, Tuesday, Wednesday,
Thursday, Friday, Saturday};
and the above code blows up. This demonstrates that
an enum is not a range.


So a programmer must decide exactly what is needed: either
named constants not necessarily related, or a set, or
a range. For sets and ranges, one should use a class.
As for using enum versus #defines or const, I'll leave
that up to this group for discussion. I know that #define
does not have scoping. "const" variables inside a class
definition, cannot be used to specify the size of an array
and also need to be initialized somewhere; whereas an
enum inside a class can be used to specify the size of
an array.

Now that I've examined the enum more closely, I will
not use it for implied sets or ranges.

--
Thomas Matthews

C++ newsgroup welcome message:
http://www.slack.net/~shiva/welcome.txt
C++ Faq: http://www.parashift.com/c++-faq-lite
C Faq: http://www.eskimo.com/~scs/c-faq/top.html
alt.comp.lang.learn.c-c++ faq:
http://www.raos.demon.uk/acllc-c++/faq.html
Other sites:
http://www.josuttis.com -- C++ STL Library book
 
A

Arne Adams

Thomas Matthews said:
Given:
Fried_Tomato automobile(Torque);
One could issue this statement:
cout << automobile << '\n';
and have the value of 3 printed out.
Taking this one more step:
automobile += 7;
This won't compile on a conforming compiler: an integer is not implicitly
convertible to an enum.
Punkin james;
is the same as
int james;
no - see above.

This explains the above behavior of automobile.

The above behavior also shows that an enum is not a set.
At least when you do not need different names for the same value, an enum is a
typed constant finite set of values.
Let us extend this confusion by comparing an enum to a
range. First a definition of a range {mine}:
A range is a contiguous, sequence of integers
from a beginning value to an ending value, inclusive,
within the capacity of the int type.
So many programmers often use the enum to represent a
range of values:
enum Weekday {Sunday, Monday, Tuesday, Wednesday,
Thursday, Friday, Saturday};
The range is Sunday .. Saturday; values 0 .. 7.

but here the values are completely irrelevant as long as they preserve the order
implied by the definition of the enum.
A loop that has to do something foreach weekday could look like
for(Weekday day = Sunday; day <= Saturday; ++day)
// ++day won't compile with current enums.
If the programmer had to provide values for the enumerated constants (for
bitmasks or whatever) I would still like the above loop to work or better the
variation where I do not need to know that Sunday is the first and Saturday the
last weekday (could as well let the week start with Monday and end with Sunday):
for(Weekday day = begin<Weekday>(); day <= end<Weekday>(); ++day)

The weekday enum would be considered contiguous regardless of the difference
between consecutive values in that enum.
(provided, of course that all weekdays are present and no more than these).
Yes, this is where many programs crash. I could make
one subtle change:
enum Weekday {Sunday, Monday = 15, Tuesday, Wednesday,
Thursday, Friday, Saturday};
and the above code blows up. This demonstrates that
an enum is not a range.
Yes again that is why I prefer the iteration proposed above.
So a programmer must decide exactly what is needed: either
named constants not necessarily related, or a set, or
a range.
or a lightweight typed constant set like an enum is.
 
A

Arijit

Is there any chance your enums are going to print their name
instead of value when I output them ? I mean

#include <iostream>
using namespace std;

enum color
{
red,
green,
blue,
};

int main()
{
color cl = green;
cout << cl; // prints green instead of 1 ?
}

That is the only thing I miss about enums - I have to write
a separate function for each enum if I ever want to output
the string literal for the enum.
Which brings me to a question - Is it possible to overload
a function based on enum ?

-Arijit
 
P

Peter van Merkerk

Arijit said:
Is there any chance your enums are going to print their name
instead of value when I output them ? I mean

#include <iostream>
using namespace std;

enum color
{
red,
green,
blue,
};

int main()
{
color cl = green;
cout << cl; // prints green instead of 1 ?
}

Try this:

#include <iostream>

using namespace std;

enum Color
{
red, green, blue
};

ostream& operator<<(ostream& out, Color color)
{
switch(color)
{
case red: out << "Red";
break;
case green: out << "Green";
break;
case blue: out << "Blue";
break;
default: out << "Huh???";
break;
}

return out;
}


int main()
{
Color color = blue;
cout << color << endl;
return 0;
}
 
A

Arijit

Peter van Merkerk said:
Try this:

#include <iostream>

using namespace std;

enum Color
{
red, green, blue
};

ostream& operator<<(ostream& out, Color color)
{
switch(color)
{
case red: out << "Red";
break;
case green: out << "Green";
break;
case blue: out << "Blue";
break;
default: out << "Huh???";
break;
}

return out;
}


int main()
{
Color color = blue;
cout << color << endl;
return 0;
}

Thanks for the example, but I still have to write the
switch statement. I specifically want to avoid that.
My main gripe is - the the symbolic names for my enum constants are
already in the program - why do I have to write them twice ?
But I guess thats not possible without using macros, and we all
know better than to use them.

-Arijit
 
P

Peter van Merkerk

Arijit said:
Thanks for the example, but I still have to write the
switch statement. I specifically want to avoid that.
My main gripe is - the the symbolic names for my enum constants are
already in the program - why do I have to write them twice ?
But I guess thats not possible without using macros, and we all
know better than to use them.

Over the years I have rarely had the need to literally output the names
of symbolic enum constants. Usually there isn't an exact match between
the internal enumerator identifiers and the string representation I
would like to see. Actually I prefer to keep the things that are to
presented to the user separate from the rest of my code. The only cases
in which automatic enum to string conversion would be useful for me is
in debug logs.

Of course YMMV, and if you really think it would be a big time saver you
could make a code generator that generates something like the function
above based on an enum definition. Integrate the code generator into
your make file and you don't have to worry about it again.
 
V

Victor Bazarov

Arijit said:
[...]
My main gripe is - the the symbolic names for my enum constants are
already in the program - why do I have to write them twice ?
But I guess thats not possible without using macros, and we all
know better than to use them.

Have you ever considered that there are plenty of "symbolic
names" around in the program that are NEVER available for UI
_unless_ you "write them twice"? Class names, variable names,
function names, and so on...
 
P

Peter van Merkerk

Victor said:
Arijit said:
[...]
My main gripe is - the the symbolic names for my enum constants are
already in the program - why do I have to write them twice ?
But I guess thats not possible without using macros, and we all
know better than to use them.


Have you ever considered that there are plenty of "symbolic
names" around in the program that are NEVER available for UI
_unless_ you "write them twice"? Class names, variable names,
function names, and so on...

Actually class names are usually available as string via the typeid()
operator. Unfortunately the standard does not specify how that string
should look like; a complying but not very useful implementation could
return just empty strings. So it is really only useful as a debugging aid.
 
V

Victor Bazarov

Peter said:
Victor said:
Arijit said:
[...]
My main gripe is - the the symbolic names for my enum constants are
already in the program - why do I have to write them twice ?
But I guess thats not possible without using macros, and we all
know better than to use them.



Have you ever considered that there are plenty of "symbolic
names" around in the program that are NEVER available for UI
_unless_ you "write them twice"? Class names, variable names,
function names, and so on...


Actually class names are usually available as string via the typeid()
operator. Unfortunately the standard does not specify how that string
should look like; a complying but not very useful implementation could
return just empty strings. So it is really only useful as a debugging aid.

Well, _usually_ doesn't really cut it, does it? No guarantees only
guarantees absence. Function names are also often available but not
in the form I'd like (I mean that they exist in the OBJ files and in
the shared objects, or DLLs as they are often called, and I _could_
extract them if I wanted to)...
 

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

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top