|
|
| Force types to unsigned |
|
OnlineCop
Member #7,919
October 2006
|
enum eMagicTypes { M_INVALID = 0, // always first M_HEAL, M_DETOX, M_REVIVE, ... M_FIRE, M_WATER, NUM_MAGIC_TYPES // always last }; With stringent compiler flags, I sometimes get warnings like warning: comparison between signed and unsigned as it's considering all of the M_* types to be signed values. I know that if you were to use #define's, you can append a "U" at the end of the number to force it to an unsigned type: #define MAX_SPELLS 256U
Is there a way to "force" this on enums? Do I need to manually assign their values (and hence losing half of the reason for using enums in the first place), or can I set the first value to enum eMagicTypes { and all the rest of the numbers become the same type? Or what about this: enum eMagicTypes { M_INVALID = -1, // always first
Would "M_INVALID" be set to a signed value and M_HEAL through NUM_MAGIC_TYPES set to unsigned? Or does M_INVALID simply become the equivalent to
|
|
torhu
Member #2,727
September 2002
|
I'll assume you're using C. If it's C++, some of this is works a bit differently. enum values in C are always signed ints, you can't change that. Just go with something like this: 1typedef enum MagicType
2{
3 M_INVALID = 0, // always first
4
5 M_HEAL,
6 M_DETOX,
7 M_REVIVE,
8 ...
9 M_FIRE,
10 M_WATER,
11
12 NUM_MAGIC_TYPES // always last
13} MagicType;
14
15MagicType magic = M_HEAL;
16
17// this version saves some typing, but
18// the one above is clearer
19int magic = M_HEAL;
Or maybe magic_t, where 't' means typedef or type. It's shorter. And you should avoid mixing unsigned and signed types in expressions unless you really have to. Using signed ints for everything is ok, even for purposes where negative values are not used. It's easier that way. |
|
Kitty Cat
Member #2,815
October 2002
|
Aren't enums supposed to be unsigned values by default? In C++0x/1x, FWIW, you should be able to do: enum eMagicTypes : unsigned int { ... };
In C, it might not be a bad idea to use -- |
|
Thomas Fjellstrom
Member #476
June 2000
|
IIRC in gcc at least you can do: enum foo { ValueOne = 123UL, }; And it should make ValueOne an unsigned long. But according to the standard (IIRC), enum values are signed integers. -- |
|
OnlineCop
Member #7,919
October 2006
|
The code I'm rewriting is done in C, so I'm following that construct. I've read this: chollida1 said: Keep in mind that your assuming an enum is the size of an int. It doesn't have to be:). YOu can force it to be atleast as large as an in by assigning an enum entry to have a value greater than a short or word. An enum can be the size of a char or a int16 or an int 32 or an int64. However, that was only another programmer whose expertise is questionable in this context. In ANSI C, the expressions that define the value of an enumerator constant always have int type; thus, the storage associated with an enumeration variable is the storage required for a single int value. An enumeration constant or a value of enumerated type can be used anywhere the C language permits an integer expression. ... The constant-expression must have int type and can be negative. For C++, MSDN says this: enum [tag] [: type] {enum-list} [declarator]; // for definition of enumerated type ... type is the underlying type of the identifiers. This can be any scalar type, for example, signed or unsigned versions of int, short, or long. bool or char is also allowed. Further reading gave me this (which sorta repeats the first quote's information): StackOverflow website said: Also, the standard says that it is "implementation-defined which integral type is used as the underlying type for an enum, except that it shall not be larger than int, unless some value cannot fit into int or an unsigned int". ... The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration. It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.
So should I just always cast the enum value to an unsigned int variable if I want to ensure that it'll be seen as an unsigned? 1enum eMagicTypes
2{
3 M_INVALID = -1, 4
5 M_HEAL,
6 M_DETOX,
7 M_REVIVE,
8 ...
9 M_FIRE,
10 M_WATER,
11
12 NUM_MAGIC_TYPES // always last
13};
14
15unsigned int my_magic = M_INVALID; will I get another warning: comparison between signed and unsigned or will it actually just take the value as though I had explicitly cast it: unsigned int my_magic = (unsigned int)M_INVALID;
|
|
X-G
Member #856
December 2000
|
You'll probably get another warning, but that's just the way it goes. Is casting it a major problem? You still need to cast whenever you want to make an enum out of it again from the unsigned int. Specifying the underlying type for an enum is a C++0x feature. -- |
|
OnlineCop
Member #7,919
October 2006
|
X-G said: Specifying the underlying type for an enum is a C++0x feature. Huh... that second MSDN quote I pasted above said only that it was "C++" without mention of C++0x. I wonder if it's a typo on their side, a quirk of only the MSVC compiler (as in, interpreting the standards to fit only their compiler), or just a little-known C++ feature? Casting isn't much of a problem; I have to cast all over the place anyway. I'm rewriting most of the code anyway to get rid of as many signed ints as possible, such as: --row; if (row < 0) row = NUM_ROWS - 1; being converted to if (row > 0) --row; else row = NUM_ROWS -1; As was mentioned in earlier threads, the second method will be more optimized than the first. The same kind of construct is going on with me adding all of these enums: if (magic > M_INVALID) --magic; else magic = NUM_MAGIC_TYPES; The problem with this, obviously, is that "magic" isn't going to be an enum type: it has to be an integer type. But what I was really hoping to do is get rid if the two-sided tests: int magic; ... if (magic > M_INVALID && magic < NUM_MAGIC_TYPES) do_something(magic); // magic is now "valid" in favor of using only unsigned values, and one-sided tests: unsigned int magic; ... if (magic < NUM_MAGIC_TYPES) do_something(magic); If "magic" were to be assigned to M_INVALID and M_INVALID were set to -1, I guess that this second check would be sufficient, as (unsigned int)-1 is > NUM_MAGIC_TYPES...
|
|
anonymous
Member #8025
November 2006
|
if (magic > M_INVALID) The problem with this comparison using unsigned values is that it is always false: (unsigned)-1 == UINT_MAX. The explicit cast just shuts up the compiler but doesn't remove the problem. |
|
torhu
Member #2,727
September 2002
|
OnlineCop said: Casting isn't much of a problem; I have to cast all over the place anyway.
If you write your code in a way that takes advantage of the type system as much as possible, instead of fighting it with casts, the compiler will be able to catch more bugs for you. Quote: I'm rewriting most of the code anyway to get rid of as many signed ints as possible, such as: --row; being converted to if (row > 0) As was mentioned in earlier threads, the second method will be more optimized than the first. Sure about that? The first method has less code, and needs only one branch instead of two. Doing --row unconditonally is very cheap, branching is more expensive. Not that I'm an optimization expert, but I'd go with the first one. I could tell you that you shouldn't care about these details until you know they are an actual problem, but I know it's fun, so I won't. Anyway, a common pattern in C code for selecting what to do based on enum types is using a switch, like this: 1void use_magic(enum MagicType magic)
2{
3 switch (magic) {
4 case M_INVALID:
5 /* do nothing or print error? */
6 break;
7 case M_HEAL:
8 /* healing power code here */
9 break;
10 case M_DETOX:
11 /* detox code goes here */
12 break;
13
14 /* handle other magic types here... */
15
16 default:
17 fprintf(stderr, "Error: invalid magic type value %d\n", magic);
18 }
19}
As you can see, you don't have to test for invalid values explicitly, you get it for free when using a switch statement. If you still need to test the value, you can use a macro (or inline function) to shorten your code, and avoid duplicating this test: #define CHECK_MAGIC(m) ((m) > M_INVALID && (m) < NUM_MAGIC_TYPES) if (CHECK_MAGIC(magic);) /* ok to use this value */ else /* bad magic value */ Using unsigned int for this would almost certainly be false economy, especially if it means adding casts. It's higly unlikely to give you any more frames per second, if that's what you're after. |
|
OnlineCop
Member #7,919
October 2006
|
switching is how I manage the individual spell effects during runtime. I've been using enumed values as integers when reading in from savefiles, though. If I were to receive a value < 0 or >= NUM_MAGIC_TYPES, I would know that the data is invalid. Otherwise, just read it into my variable: 589unsigned int load_player(void)
590{
591 unsigned int i;
592 unsigned int tmp;
593 FILE *edat;
594 s_player *player;
595
596 edat = fopen(file_manager(PLAYER_SAVE), "r");
597 if (!edat)
598 {
599 message_log("Cannot load file %s.\n", file_manager(PLAYER_SAVE));
600 return 0;
601 }
602
603 player = (s_player *) malloc(sizeof(s_player));
604
605 /* Savegame version: major, minor */
606 fscanf(edat, "%u", &tmp);
607 player->version_major = tmp;
608 fscanf(edat, "%u", &tmp);
609 player->version_minor = tmp;
610
611 if (player->version_major == GAME_VERSION_MAJOR &&
612 player->version_minor == GAME_VERSION_MINOR)
613 {
614 /* Player has 16 spell slots */
615 for (i = 0; i < 16; ++i)
616 {
617 fscanf(edat, "%u", &tmp);
618 if (tmp < NUM_MAGIC_TYPES) 619 player->spells[i] = tmp;
620 else
621 player->spells[i] = M_INVALID;
622 }
623
624 ...
625
626 fclose(edat);
627 return 1;
628 }
629
630 fclose(edat);
631 return 0;
632}
Here, it may be impractical to throw an entire switch() statement in to check that the value was between M_HEAL and M_WATER.
|
|
anonymous
Member #8025
November 2006
|
So what's the problem there? Just cast the enum value to unsigned. Anyway, wouldn't you also need to check that the file contains numbers in the first place? |
|
ImLeftFooted
Member #3,935
October 2003
|
Kitty Cat said: In C++0x/1x
Has it come to that? What's taking those guys so long? I've been finding more and more cases for a move command. Is gcc implementing any of the features in advance? |
|
Kitty Cat
Member #2,815
October 2002
|
Dustin Dettmer said: Is gcc implementing any of the features in advance? Some of them, yes, using -std=c++0x or -std=gnu++0x. -- |
|
OnlineCop
Member #7,919
October 2006
|
anonymous said: So what's the problem there? Just cast the enum value to unsigned.
Might have to do that. Casting is ugly, though, unless it's necessary. I just hate having 2-3 pages of those "signed to unsigned" warnings for big projects.
|
|
anonymous
Member #8025
November 2006
|
OnlineCop said: Might have to do that. Casting is ugly, though, unless it's necessary. I just hate having 2-3 pages of those "signed to unsigned" warnings for big projects. Or just use int's and compare for range. And write a macro for that if it is too much typing. And if you get "signed to unsigned" warnings, think about it whether your code is not indeed in error because of those. |
|
Evert
Member #794
November 2000
|
I would do what torhu posted in his first post: typedef the enum and then make variables that are supposed to take values from the enum the type of the enum. |
|
OnlineCop
Member #7,919
October 2006
|
Evert said: I would do what torhu posted in his first post: typedef the enum and then make variables that are supposed to take values from the enum the type of the enum. That's about what I've been doing. But it's hard to use an enum type when reading in from disk (see highlighted line 618 above), since there's no way that the compiler can validate that the value read in is okay.
|
|
Tobias Dammers
Member #2,604
August 2002
|
OnlineCop said: But it's hard to use an enum type when reading in from disk (see highlighted line 618 above), since there's no way that the compiler can validate that the value read in is okay. You can also cast to an enum type. You'll have to do the validation yourself though. --- |
|
torhu
Member #2,727
September 2002
|
OnlineCop said: Evert said: I would do what torhu posted in his first post: typedef the enum and then make variables that are supposed to take values from the enum the type of the enum. That's about what I've been doing. But it's hard to use an enum type when reading in from disk (see highlighted line 618 above), since there's no way that the compiler can validate that the value read in is okay.
|
|
|