Allegro.cc - Online Community

Allegro.cc Forums » Off-Topic Ordeals » Force types to unsigned

Credits go to anonymous, ImLeftFooted, Kitty Cat, Thomas Fjellstrom, torhu, and X-G for helping out!
This thread is locked; no one can reply to it. rss feed Print
Force types to unsigned
OnlineCop
Member #7,919
October 2006
avatar

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
{
M_INVALID = 0U,
M_HEAL, ... NUM_MAGIC_TYPES // always last };

and all the rest of the numbers become the same type?

Or what about this:

enum eMagicTypes
{
  M_INVALID = -1, // always first

M_HEAL = 0U,
M_DETOX, M_REVIVE, ... M_FIRE, M_WATER, NUM_MAGIC_TYPES // always last };

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
unsigned int M_INVALID = (unsigned int)-1;
???

torhu
Member #2,727
September 2002
avatar

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:

#SelectExpand
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
avatar

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
static const unsigned int ...
instead, since C won't provide any type safety for enums (beyond warnings of not handling certain enumerants in switches).

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

Thomas Fjellstrom
Member #476
June 2000
avatar

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.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

OnlineCop
Member #7,919
October 2006
avatar

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):

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?

#SelectExpand
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
avatar

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.

--
Since 2008-Jun-18, democracy in Sweden is dead. | 悪霊退散!悪霊退散!怨霊、物の怪、困った時は ドーマン!セーマン!ドーマン!セーマン! 直ぐに呼びましょう陰陽師レッツゴー!

OnlineCop
Member #7,919
October 2006
avatar

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
avatar

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;
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.

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:

#SelectExpand
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
avatar

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:

load_player_save.c#SelectExpand
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
avatar

Kitty Cat said:

In C++0x/1x

Has it come to that? What's taking those guys so long? :P

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
avatar

Is gcc implementing any of the features in advance?

Some of them, yes, using -std=c++0x or -std=gnu++0x.

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

OnlineCop
Member #7,919
October 2006
avatar

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. :P

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
avatar

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.
To me, that sounds like the most "correct" way to do this.

OnlineCop
Member #7,919
October 2006
avatar

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
avatar

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.

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

torhu
Member #2,727
September 2002
avatar

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.

Go to: