![]() |
|
Basic C++ question: enum |
Karadoc ~~
Member #2,749
September 2002
![]() |
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. 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: It's not a big deal that that isn't allowed. What is a big deal is this: 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
![]() |
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
![]() |
axilmar
Member #1,204
April 2001
|
Karadoc ~~ said: 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: 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
![]() |
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. -- 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
|
Karadoc ~~ said: 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
![]() |
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; 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
![]() |
Karadoc ~~ said: From this [enel.ucalgary.ca] reference (chosen arbitrarily). Quote: There is an implicit conversion from any enum type to int. 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. -- |
Karadoc ~~
Member #2,749
September 2002
![]() |
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
![]() |
Karadoc ~~ said: 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. -- |
bamccaig
Member #7,536
July 2006
![]() |
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. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
type568
Member #8,381
March 2007
![]() |
I've that feeling, that the more useless a feature is the more it is discussed.
|
Arthur Kalliokoski
Second in Command
February 2005
![]() |
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
![]() |
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
![]() |
That's the stuff that needs removing.
|
Kitty Cat
Member #2,815
October 2002
![]() |
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. -- |
axilmar
Member #1,204
April 2001
|
Karadoc ~~ said: 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: 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'
|
bamccaig
Member #7,536
July 2006
![]() |
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. 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? -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
Karadoc ~~
Member #2,749
September 2002
![]() |
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
|
Karadoc ~~ said: 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> I thought so too, but the compiler complains if I put 'typename' in front of 'T::VALUE()'. |
Oscar Giner
Member #2,207
April 2002
![]() |
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
![]() |
That's good news about C++0x. I look forward to it. ----------- |
Tobias Dammers
Member #2,604
August 2002
![]() |
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++. 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). --- |
bamccaig
Member #7,536
July 2006
![]() |
Tobias Dammers said: 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. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
Tobias Dammers
Member #2,604
August 2002
![]() |
I'm pretty sure a determined programmer will find a creative way around that, too. --- |
|