Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Basic C++ question: enum

Credits go to axilmar and LennyLen for helping out!
This thread is locked; no one can reply to it. rss feed Print
Basic C++ question: enum
Karadoc ~~
Member #2,749
September 2002
avatar

The enum keyword seems like it would neat and convenient tool for me, except that it doesn't behave the way I'd expect or want...

From this reference (chosen arbitrarily).

the site said:

There is an implicit conversion from any enum type to int.
...
On the other hand, there is not an implicit conversion from int to an enum type

To me, this seems backwards. Suppose I had something like this:

enum Month {jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec};

It seems to me that it would be fairly natural to want to do something like this:
Month this_month = 4;

It's not a big deal that that isn't allowed. What is a big deal is this:
int an_unrelated_number = mar; //lolwut?
I just don't understand why this is allowed! I'd like to use enum, but I don't what my enums to clutter up the namespace in this way.

So my question is this: is there some trick I can use to define an enum, like the Month thing above, without implicitly defining a bunch of constant ints that I won't ever need outside of the Month enum.

-----------

type568
Member #8,381
March 2007
avatar

enum can be converted to int, int cannot be converted to enum at least because two different enum values can hold the same integer value.

LennyLen
Member #5,313
December 2004
avatar

Tobias answered this question for me once here, and then again a few days ago here.

axilmar
Member #1,204
April 2001

I just don't understand why this is allowed! I'd like to use enum, but I don't what my enums to clutter up the namespace in this way.

You can always define enums within a class/struct/namespace:

#SelectExpand
1struct DAY { 2 enum VALUE { 3 MON, 4 TUE, 5 WED, 6 THU, 7 FRI, 8 SAT, 9 SUN 10 }; 11}; 12 13//or: 14namespace DAY { 15 enum VALUE { 16 MON, 17 TUE, 18 WED, 19 THU, 20 FRI, 21 SAT, 22 SUN 23 }; 24}

And then you can happily use it like this:

int day1 = DAY::MON;
Day::VALUE day2 = DAY::WED;

You can also make completely typesafe enums, by defining a class that holds the values and a template class that contains the enumerated value:

//enum template
template <class T> class Enum : public T {
public:
    //bla bla constructors, destructor, setters, getters, etc.

private:
    typename T::VALUE m_value;
};

//only if DAY is not a namespace
typedef Enum<DAY> Day;

And then you have a completely typesafe enum. For example, this is not possible:

int d = Day::MON;

while this is:

Day d = Day::MON;

Personally, I prefer the class solution over the namespace solution because I always need to encapsulate retrospection information in my enums, so as that the GUI is automatically created from the enums ;-).

Karadoc ~~
Member #2,749
September 2002
avatar

The reason for not allowing an implicit int -> enum conversion seems ... reasonable; and axilmar's examples look like they do just what I was asking for. So thanks for all that.

Also, I did a little bit of testing with regard to the enum namespace thing...

enum Day {mon = 0, tue, wed, thr, fri};
//...
{
  int test = tue; // this is allowed, and now test == 1
  Day today = tue;
}
{
  int test = tue;
  int tue = 5;
  int test2 = tue;
  // now test == 1 and test2 == 5; Everything is fine so far
  Day today = tue; // but this is not allowed
}

So the result of all that is that although the enum stuff does hang around even when it isn't wanted, it does a decent job of staying out of the way.

For what I'm doing, I think I'll try axilmar's class thing. (edit - ** see below)

--

type568, I don't think what you said about multiple enum values being the same int value is correct – because of this:

enum Day {mon = 0, tue = 0, wed, thr, fri};

Day today = mon;

if (today == tue)
    cout << "tuesday!";

This code results in "tuesday!" being printed. The point is that although multiple labels can apply to one number, it is still just one number. So this doesn't create a problem for converting ints to enums.

--
[edit]
I'm having some trouble getting that class solution to work. I guess I didn't really understand what I was meant to do. Here's what I've got so far:

#SelectExpand
1struct DAY 2{ 3 enum VALUE { mon, tue, wed, thu, fri}; 4}; 5 6template <typename T> class Enum : public T 7{ 8 public: 9 operator typename T::VALUE() { return m_value; } 10 void operator=(typename T::VALUE v) { m_value = v; } 11 12 private: 13 typename T::VALUE m_value; 14}; 15typedef Enum<DAY> Day; 16 17// ... 18Day today; 19int x; 20today = Day::mon; // allowed, which is good 21x = Day::mon; // also allowed, which is bad.

-----------

axilmar
Member #1,204
April 2001

I'm having some trouble getting that class solution to work.

You should not provide this operator:

operator typename T::VALUE() { return m_value; }

Because C++ sees it and converts your class to int.

Karadoc ~~
Member #2,749
September 2002
avatar

Ok, but even with that line commented out, x = Day::mon; is compiled without warnings or errors. Actually I don't understand what would prevent it from being allowed. This 'Day' class, made using the template, has all the properties that struct DAY has and they are all public. So if DAY::mon works, then Day::mon will also work.

I'm trying to achieve what you described here:

axilmar said:

And then you have a completely typesafe enum. For example, this is not possible:

int d = Day::MON;
while this is:

Day d = Day::MON;

If you've done this in your own projects, would you mind posting a complete example? Because although I thought I understood how it worked before, I don't see any way forward now.

-----------

Kitty Cat
Member #2,815
October 2002
avatar

From this [enel.ucalgary.ca] reference (chosen arbitrarily).

Quote:

There is an implicit conversion from any enum type to int.
...
On the other hand, there is not an implicit conversion from int to an enum type

To me, this seems backwards. Suppose I had something like this:

An enum is an integer (you can implicitly cast enum->int), but an integer isn't an enum (you can't implicitly cast int->enum). If you think about it, it makes sense. Lets say you had code like this:

enum Foo {
   VAL1 = 1,
   VAL2 = 3
};

void Func(int ival)
{
    Foo eval = ival;
    ...
}

Now, what should happen if you call Func(0); or Func(2);? If you try to forcefully cast it, you essentially break the type, since it has a value that's not part of its enumeration values.

You could argue it should know if given a determinable value, but that would cause an inconsistency as it could accept some integer variables and not others.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

Karadoc ~~
Member #2,749
September 2002
avatar

Right, that seems fair. But I think an equally fair alternative would be to just treat enum as an int in which some particular values have names. So any int value is allowed by the enum, but not all values have special names. That's how it works in C, and I think it's fine*.

However, I completely understand the reasons behind not allowing implicit conversion from int to enum, and I think the reasons are sensible and fair. I just expected that it would be the same as in C, for consistency, but it isn't.

The main thing I don't like is the other side of things - the fact that the labels can be used outside of the enum (C has the same problem). I want the enum labels to apply only to the enum. I don't see any reason to spew the enum definitions across the entire namespace, and it prevents me from doing stuff like this:

enum Type1 {empty = 0, rock, sand, water, grass};
enum Type2 {empty = 0, road, water, tree, dirt};
// error: conflicting declaration 'water'
// but all I want is for 'water' to refer to 3 when I'm using Type1, and 2 when using Type2. There should be no conflict. 

If I wanted to define a bunch of constant ints, I'd define a bunch of constant ints... like this const int empty = 0, rock = 1, sand = 2, water = 3, grass = 4; // not what I want

-----------

Kitty Cat
Member #2,815
October 2002
avatar

But I think an equally fair alternative would be to just treat enum as an int in which some particular values have names. So any int value is allowed by the enum, but not all values have special names. That's how it works in C, and I think it's fine*.

I like to think of enums as strong integer types, such that an enum variable will only contain a valid enumeration value for it. With GCC, for example, if you do this:

enum Foo {
   VAL1, VAL2, VAL3
};

void Func(Foo val)
{
    switch(val)
    {
    case VAL1:
        ...
    case VAL2:
        ...
    }
}

It will throw a warning that VAL3 is not handled. And given that an enum can't contain an invalid enumeration value (unless you fail to initialize it, or forcefully cast a value), it's very useful for keeping a variable restrained to specific values. C++'s stronger type safety just helps enforce that.

EDIT:

Quote:
enum Type1 {empty = 0, rock, sand, water, grass};
enum Type2 {empty = 0, road, water, tree, dirt};
// error: conflicting declaration 'water'
// but all I want is for 'water' to refer to 3 when I'm using Type1, and 2 when using Type2. There should be no conflict.

You can use a namespace:

namespace nType1 {
    enum Type1...;
};
using namespace nType1;

namespace nType2 {
    enum Type2...;
};
using namespace nType2;

Though you'll have to use nType1::water or nType2::water, if there's a duplicate enum value name. You can also use operator overrides to handle casts from a Type1 enum value to a Type2 one, and vice-versa.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

bamccaig
Member #7,536
July 2006
avatar

Kitty Cat said:

And given that an enum can't contain an invalid enumeration value (unless you fail to initialize it, or forcefully cast a value), it's very useful for keeping a variable restrained to specific values.

In C and C++, enum is just loosely controlled syntactic sugar over an int. The type doesn't mean anything in particular, except that certain values can be accessed by a meaningful name (albeit, the namespace issue pretty much forces the name to be uber long or potentially conflicting). enum types are not very type safe at all. You certainly can't rely on them. They basically provide the programmer with a semantically sensible alternative to magic numbers. Essentially, identical to a set of const ints and an int; except that C++ requires a cast. Requiring a cast is a good thing, but it still isn't something that you can rely on nobody doing. For that it would need to be impossible to cast to an enum type, which would have its own downfalls. Validation is your friend. :)

type568
Member #8,381
March 2007
avatar

I've that feeling, that the more useless a feature is the more it is discussed.

Arthur Kalliokoski
Second in Command
February 2005
avatar

type568 said:

I've that feeling, that the more useless a feature is the more it is discussed.

Of course! Nobody argues about whether stuff that's obviously necessary should exist.

They all watch too much MSNBC... they get ideas.

Karadoc ~~
Member #2,749
September 2002
avatar

In this case, I'm hoping that our discussions will reveal a way of making enum less useless (more useful). Maybe people discuss useless stuff in general because that's the stuff that needs improving.

-----------

type568
Member #8,381
March 2007
avatar

That's the stuff that needs removing.

Kitty Cat
Member #2,815
October 2002
avatar

bamccaig said:

Requiring a cast is a good thing, but it still isn't something that you can rely on nobody doing. For that it would need to be impossible to cast to an enum type, which would have its own downfalls.

You can say that about anything, really. If you can't rely on the code being safe with enum values (ie, not casting an unverified int value), then by the same token you couldn't rely on the code being safe with object pointers types (ie, not casting from a potentially incompatible pointer type to another; particularly from void*). Both require you to go out of your way to break it, in C++.

While it is true that pre-existing code may be more likely to break C++ enums than pointers, because people misunderstand their purpose and think they're just named integers or const int variables, it's a different case when you are in control of the code base. If you control the code, it's not difficult to make sure you don't cast unverified values (or preferrably not cast at all) from integers. When handled properly, enums can make the code more elegant and less error-prone.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

axilmar
Member #1,204
April 2001

If you've done this in your own projects, would you mind posting a complete example? Because although I thought I understood how it worked before, I don't see any way forward now.

Here is the code:

#SelectExpand
1class DAY { 2public: 3 enum VALUE { 4 MON, 5 TUE, 6 WED, 7 THU, 8 FRI, 9 SAT, 10 SUN 11 }; 12}; 13 14 15template <class T> class Enum : public T { 16public: 17 Enum(typename T::VALUE value = T::VALUE()) : m_value(value) { 18 } 19 20 Enum<T> &operator = (typename T::VALUE value) { 21 m_value = value; 22 return *this; 23 } 24 25private: 26 typename T::VALUE m_value; 27}; 28 29 30typedef Enum<DAY> Day; 31 32 33int main() { 34 Day day = Day::MON; 35 36 int i; 37 Day d; 38 39 i = day; 40 d = day; 41 42 return 0; 43}

And here is the compiler error:

Quote:

1>e:\temp\test_enum\main.cpp(39) : error C2440: '=' : cannot convert from 'Day' to 'int'
1> No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

bamccaig
Member #7,536
July 2006
avatar

Kitty Cat said:

You can say that about anything, really. If you can't rely on the code being safe with enum values (ie, not casting an unverified int value), then by the same token you couldn't rely on the code being safe with object pointers types (ie, not casting from a potentially incompatible pointer type to another; particularly from void*).

You can't. :P That's why modern languages, like Java and C#, make it more difficult or impossible to do such things. You can never really rely on them being correct. Unfortunately, most code is written by many people and it can be very difficult to spot such evils, even if you do review patches, etc.

The best thing to do is to validate when possible. You can't validate a pointer (aside from ensuring it isn't 0), but you can damn sure validate enum types. The easiest way is probably to handle them with a switch statement with a default case that handles the error or design the program so that an invalid enum value has no side effects (when appropriate). All I'm saying is that you can't rely on them and that if you want your code to be closer to bullet proof then you should assume that the value can be invalid and handle that case.

axilmar said:

T::VALUE()

Is that a function call? :o WTF is going on there? :P

Karadoc ~~
Member #2,749
September 2002
avatar

ah. I understand now. What I had was on the right track but I thought that it would prevent i = Day::MON;, which it doesn't. But it does prevent i = day;.. So it isn't a complete solution to my complaint, but it is an improvement over the usual enum behavior. Thanks for that.

Quote:

Is that a function call? WTF is going on there?

That's meant to be the default initializer of T::VALUE. So it should result in the default value of the enum. Actually, I the line needs to be Enum(typename T::VALUE value = typename T::VALUE()) : m_value(value) {}, I think. I don't really know why it needs the "typename" keyword, but the compiler was complaining without it.

-----------

axilmar
Member #1,204
April 2001

but I thought that it would prevent i = Day::MON

Unfortunately, it's not possible.

In my never-to-be-released language, my enums where strongly typed sets of values and therefore converting from enum to integer or integer to enum wouldn't be possible ;-).

Quote:

So it should result in the default value of the enum.

Actually, it just sets the value to 0, which may or may not be the default value. The correct approach would be to set the value to the enum's first member, but since enums are ints, it is just set to 0.

<quote>
the line needs to be Enum(typename T::VALUE value = typename T::VALUE()) : m_value(value) {}
</code>

I thought so too, but the compiler complains if I put 'typename' in front of 'T::VALUE()'.

Oscar Giner
Member #2,207
April 2002
avatar

C++0x seems to solve all these problems. If you declare an enum with enum class then it's strongly typed (no conversion from or to integers) and the scope of the values is inside the enum name.

Karadoc ~~
Member #2,749
September 2002
avatar

That's good news about C++0x. I look forward to it. :)
In fact, maybe it's time I updated my gcc (which is currently v3.4.5). By the sounds of this I'd be able to start using this feature right away. -- but then I'd have to recompile allegro (wouldn't I?)

-----------

Tobias Dammers
Member #2,604
August 2002
avatar

bamccaig said:

That's why modern languages, like Java and C#, make it more difficult or impossible to do such things.

In C#, you can cast any enum back to its underlying integer type without a compiler error; the only thing that's required is that the cast be explicit. The enum behaves exactly as in C++.
Compile and run for your pleasure:

#SelectExpand
1using System; 2 3namespace EnumCast 4{ 5 class Program 6 { 7 enum Shenanigans 8 { 9 Foo = 0, 10 Bar = 1, 11 Baz = 3 12 } 13 14 static void Main(string[] args) 15 { 16 Shenanigans s = Shenanigans.Foo; 17 Console.WriteLine("Shenanigans is: {0}", s.ToString()); 18 s = (Shenanigans)2; 19 Console.WriteLine("Shenanigans is now: {0}", s.ToString()); 20 Console.ReadKey(); 21 } 22 } 23}

Seriously, the idea is not to make things 120% foolproof. The idea is to provide programmers with tools that prevent them from accidentally doing stupid things. If you want to assign a value to an enumeration variable that's not in the enumeration, you can, but the language makes you jump through a little inconvenient hoop, which should be enough to remind you that you're not supposed to do this unless you absolutely have to (e.g., when reading enum values from a data source that has no notion of enumerations, like a binary file, or when interfacing with a closed API that provides raw integers only).

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

bamccaig
Member #7,536
July 2006
avatar

In C#, you can cast any enum back to its underlying integer type without a compiler error; the only thing that's required is that the cast be explicit. The enum behaves exactly as in C++.

I think I was actually referring to the casting of incompatible pointers part. :P

Tobias Dammers
Member #2,604
August 2002
avatar

I'm pretty sure a determined programmer will find a creative way around that, too.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Go to: