--(int)d

Y

yuu

Is this code correct C and C++?

{
enum MyEnum { ME_A, ME_B } d = ME_B;

--(int)d;
/* 'd' should be ME_A now. I added the cast because g++ would not
accept the program without it. */
}

gcc accepts it, but MS Visual C++ says that "--" needs an lvalue.
Which compiler is right according to C and C++ standards?
 
M

Michael Meissner

Is this code correct C and C++?

{
enum MyEnum { ME_A, ME_B } d = ME_B;

--(int)d;
/* 'd' should be ME_A now. I added the cast because g++ would not
accept the program without it. */
}

gcc accepts it, but MS Visual C++ says that "--" needs an lvalue.
Which compiler is right according to C and C++ standards?

The --(int)d is a GCC extension. If you use the -pendantic option, GCC will
warn you about the use of extensions:

--> gcc -O -S foo.c -pedantic
foo.c: In function `foo':
foo.c:4: warning: ISO C forbids use of cast expressions as lvalues
foo.c:4: warning: ISO C forbids use of cast expressions as lvalues
 
B

Ben Pfaff

{
enum MyEnum { ME_A, ME_B } d = ME_B;

--(int)d;
/* 'd' should be ME_A now. I added the cast because g++ would not
accept the program without it. */
}

Not correct C. The result of a cast is not an lvalue.
gcc accepts it, but MS Visual C++ says that "--" needs an lvalue.

Did you pass -ansi -pedantic to GCC? Otherwise it's not a
conforming compiler.
 
E

Ed Morton

yuu said:
Is this code correct C and C++?

{
enum MyEnum { ME_A, ME_B } d = ME_B;

--(int)d;
/* 'd' should be ME_A now. I added the cast because g++ would not
accept the program without it. */
}

gcc accepts it, but MS Visual C++ says that "--" needs an lvalue.
Which compiler is right according to C and C++ standards?

It doesn't really matter. Don't do it as you're opening up a potential
window for a future bug when someone comes along and sticks another enum
value between the existing 2. If you're using enums, don't make any
assumptions in your code about their absolute or relative numeric values
and your code will be more robust.

Ed.
 
K

Kevin D. Quitt

Quite possibly the best single way to avoid mysterious bugs is to treat
variables as they are logically intended. C does not provide string
typing, so you should.

Treat numeric variables as numeric; don't do bit operations on them.

Treat boolean variables as boolean; don't do arithmetic and don't compare
them against boolean constants.

Treat enum variables as though they can have no values except the symbolic
ones defined for them; don't increment RED to get BLUE.

Etc. (I also always initialize pointers to NULL, never compare them to
NULL (using if (!ptr)), compile with virtually all warnings turned on, and
don't type cast except when it is absolutely necessary - or to get rid of
a warning after the program is otherwise thoroughly debugged. But this is
a different issue.)

Yes, these are generalizations, and there are times when it's perfectly OK
not to follow them. If you are writing hard real-time code or device
drivers, there are times when it might be useful not to follow these
practices (e.g., a compiler that does a divide to calculate the modulo of
an integer type, and which divide is slower than the boolean masking
operation).

I believe I'm safe in saying that, in general, there's no reason not to
follow these practices. Besides, it helps make your code more portable
and (more importantly) more readable.
 
J

J. J. Farrell

Ed Morton said:
It doesn't really matter. Don't do it as you're opening up a potential
window for a future bug when someone comes along and sticks another enum
value between the existing 2. If you're using enums, don't make any
assumptions in your code about their absolute or relative numeric values
and your code will be more robust.

That depends on what you're doing and why - 'going to the next lowest
value without knowing or caring what it is' may be exactly what you
want to do. For example, an alarm state might be represented by
enum {GREEN, RED}; if I want to go to the lowest alarm state, I would
explicitly go to GREEN. If I know I'm not at the lowest state but want
to go down a state, I'd decrement the current state. As originally
written, these two would be equivalent. When someone changes the alarm
states to enum {GREEN, GREENISH_YELLOW, AMBER, REDISH_ORANGE, RED} my
decrement from RED wouldn't change the state to GREEN, but it would do
exactly what I intended.

In line with your argument, I would never decrement RED if I was trying
to get to GREEN, but it's appropriate to decrement RED if I want to get
to 'whatever comes just before RED'. This is susceptible to someone
explicitly setting the values of the enums and leaving gaps, but that
is reasonably handled by commenting the definition.
 
Y

yuu

Right, -pedantic warns me about the error. I always use -Wall, but not
-pedantic, because i like some extensions...
Compiling with g++, -pedantic has no effect, the compiler happily
accepts the program...

Well, the cast was unnecessary, i used it to make the program compile
as C++ too. But the remedy was worse than the disease!

As for the "don't use enums as integers" silly rule, i'm sorry to say
that, but i won't follow you. ;-) Even Pascal has ord, pred and succ
that work on enum types!

Anyway let me explain what i was doing: my enum has 4 directions (say,
north, east, south and west). I use -1 (or --) to turn left and +1 to
turn right (%4, obviously). I could change the enum to #defines, but i
would lose the nice 'Direction' type i had defined! Choose your
favorite:

#define EAST 0
#define SOUTH 1
#define WEST 2
#define NORTH 3

// Use the #defines above as Direction (comment needed to make the
code clearer)
typedef int Direction;

// OR //

typedef enum Direction{
EAST, SOUTH, WEST, NORTH
}Direction;
don't increment RED to get BLUE.

What if i'm writing a karate program?
enum BeltColor {WHITE_BELT, /*i don't know the colors in between,*/
BLACK_BELT};

I don't think there is anything wrong in having ordered enums.
Etc. (I also always initialize pointers to NULL, never compare them to
NULL (using if (!ptr))

Again, what's the point of !ptr if ptr is not a boolean, but a
pointer?? Pointers are to be compared to pointers, so ptr == NULL is
OK. I prefer to use ! with booleans, not with pointers (and not with
integers, either)
 
E

Ed Morton

J. J. Farrell said:
That depends on what you're doing and why - 'going to the next lowest
value without knowing or caring what it is' may be exactly what you
want to do. For example, an alarm state might be represented by
enum {GREEN, RED}; if I want to go to the lowest alarm state, I would
explicitly go to GREEN. If I know I'm not at the lowest state but want
to go down a state, I'd decrement the current state. As originally
written, these two would be equivalent. When someone changes the alarm
states to enum {GREEN, GREENISH_YELLOW, AMBER, REDISH_ORANGE, RED} my
decrement from RED wouldn't change the state to GREEN, but it would do
exactly what I intended.

But the way to do that is to wrap it in a macro or function, e.g.:

color = RAISECOLOR(color);

where you can #define:

#define RAISECOLOR(c) (int)(c) + 1;
#define LOWERCOLOR(c) (int)(c) - 1;

and then if in future you can no longer rely on the a contiguous
ordering of enum values (e.g. by adding a new value in the middle of the
range when it logically belongs at the "end" or by leaving numbering
gaps), you can change it to:

#define RAISECOLOR(c) (c) == GREEN ? YELLOW : .....;

and you can handle any necessary wrapping of values. For example, if
this was speeds of a ceiling fan, then you'd want the fan to start off
running at high speed, then go to medium, then low, then off on
successive chain pulls, so:

enum { OFF, LOW, MED, HIGH } ...
#define CHGSPEED(s) (s) == OFF ? HIGH : (s) - 1;

(ignore the double argument evaluation - it's beside the main point). I
guess the way I should've phrased my statement is that you should never
DIRECTLY perform arithmetic operations on enum values. In all of the
above, if you always just increment, or decrement, then you have to deal
with running off the end of the enum anyway.

Ed.
 
E

Ed Morton

yuu said:
Right, -pedantic warns me about the error. I always use -Wall, but not
-pedantic, because i like some extensions...
Compiling with g++, -pedantic has no effect, the compiler happily
accepts the program...

Well, the cast was unnecessary, i used it to make the program compile
as C++ too. But the remedy was worse than the disease!

As for the "don't use enums as integers" silly rule, i'm sorry to say
that, but i won't follow you. ;-) Even Pascal has ord, pred and succ
that work on enum types!

Exactly the point. You don't increment the variable, you use a diferent
method to get the succeeding or preceeding value. See my later posting
in this thread for more details.
Anyway let me explain what i was doing: my enum has 4 directions (say,
north, east, south and west). I use -1 (or --) to turn left and +1 to
turn right (%4, obviously). I could change the enum to #defines, but i
would lose the nice 'Direction' type i had defined! Choose your
favorite:

#define EAST 0
#define SOUTH 1
#define WEST 2
#define NORTH 3

// Use the #defines above as Direction (comment needed to make the
code clearer)
typedef int Direction;

// OR //

typedef enum Direction{
EAST, SOUTH, WEST, NORTH
}Direction;

Yeah, I guess I was a little hasty saying "don't ever do it". As long as
you wrap it in some pred/succ type of function or macro, then it doesn't
really affect the maintainability of the code whether you use #defines
or enums.
What if i'm writing a karate program?
enum BeltColor {WHITE_BELT, /*i don't know the colors in between,*/
BLACK_BELT};

It varies by style and club, but:

white -> yellow -> orange -> green -> blue -> purple -> brown 1 -> brown
2 -> brown 3 -> black

is fairly typical. You can, however, go straight from white to orange,
for example. It's up to whoever is performing the grading to decide what
belt you come out with. You CAN even go down a belt, so this isn't a
great example.
I don't think there is anything wrong in having ordered enums.

No there isn't, just don't rely on them keeping that order, or that
order having specific numeric values, throughout your code. If your
enums have a pred/succ relationship, then encapsulate that.

Ed.
 
J

John Bode

Is this code correct C and C++?

{
enum MyEnum { ME_A, ME_B } d = ME_B;

--(int)d;
/* 'd' should be ME_A now. I added the cast because g++ would not
accept the program without it. */
}

gcc accepts it, but MS Visual C++ says that "--" needs an lvalue.
Which compiler is right according to C and C++ standards?

As far as C is concerned, it's an error. Both the pre- and postfix
forms of "++" and "--" require an lvalue operand, and the result of a
cast expression is not an lvalue.

I'm not sure about C++, but I think the same logic applies.

This may be an extension offered by gcc. Try compiling again with the
flags -ansi -pedantic and see what it says.
 
C

Chris Torek

As far as C is concerned, [--(int)d is] an error. Both the
pre- and postfix forms of "++" and "--" require an lvalue operand,
and the result of a cast expression is not an lvalue.
(Right.)

I'm not sure about C++, but I think the same logic applies.

C++ is quite different, largely because C++ has references and
user-defined overloaded operators that can return references. A
reference is, in effect, a special kind of C++ lvalue -- and if an
operator or function returns a reference, that makes the result
of that operator or function OK for further manipulation.

Modern C++ (which is quite different from the C++ I read about in
the original Stroustrup book) has quite a few different kinds of
casts, with subtly (or even blatantly) different meanings, but in
this particular case, the result of a "C-style cast" is a reference.
This [C version] may be an extension offered by gcc.

It is. GCC's "cast as lvalue" extension is defined in a rather
complicated fashion. You may cast on the left side of an assignment
operator (ordinary =, or += -= etc.), or as an operand to the ++
and -- operators. The result is not always consistent; for instance,
if "f" has type float, (int)f = 3.5 is considered OK (it means
(int)(f = (float)(int)3.5)) but &(int)f does not do anything useful.
Of course the cast-assignment to f here is not all that useful
either -- we could just write "(int)(f = 3.0F)", which would work
in any C compiler.
 
K

Kevin D. Quitt

As for the "don't use enums as integers" silly rule, i'm sorry to say
that, but i won't follow you. ;-) Even Pascal has ord, pred and succ
that work on enum types!

They're guidelines, not hard rules. There are times not to follow them,
but it's almost never when you think it's useful. These guidelines are
for writing code that works because it's supposed to, as opposed to code
that works by accident.

In general, counting on the order of enums is not a good idea. For your
example, what happens if you turn left from EAST? What if you want to
expand what you have to include SE, NE, SW, NW, and/or points in between?
Your code has to change, and it has to change everywhere.

If you wrap direction changing in functions, then you can be safe. Let
the calling program have a token that has no meaning to it, but that it
can call the wrapper functions to manipulate and interpret (such as
providing the string that represent the current direction, or a degree
count, etc.).

C can't enforce that, though.

I don't think there is anything wrong in having ordered enums.

It's not wrong, it's just that it frequently leads to problems because
your thinking is sloppy. C's enums aren't really enums, they're a
convenient fiction with no protection. Treat them strictly as enums
(except in wrapper routines which have to know better), and you're OK.

Again, what's the point of !ptr if ptr is not a boolean, but a
pointer?? Pointers are to be compared to pointers, so ptr == NULL is OK


-------------8<-----------------
#include <stdio.h>
#include <stdlib.h>

#undef NULL
#define NULL (void *)(123)
char *foo;

int main( void )
{
printf( "foo = %p\n", foo );
if ( foo == NULL )
puts( "foo == NULL\n" );
else
puts( "foo != NULL\n" );

return 0;
}
------------->8-----------------

YMMV. !ptr is guaranteed to be true if ptr is NULL, ptr == NULL is not.
 
K

Keith Thompson

Kevin D. Quitt said:
On 9 Dec 2003 19:09:50 -0800, (e-mail address removed) (yuu) wrote: [...]
Again, what's the point of !ptr if ptr is not a boolean, but a
pointer?? Pointers are to be compared to pointers, so ptr == NULL is OK


-------------8<-----------------
#include <stdio.h>
#include <stdlib.h>

#undef NULL
#define NULL (void *)(123)
char *foo;

int main( void )
{
printf( "foo = %p\n", foo );
if ( foo == NULL )n
puts( "foo == NULL\n" );
else
puts( "foo != NULL\n" );

return 0;
}
------------->8-----------------

YMMV. !ptr is guaranteed to be true if ptr is NULL, ptr == NULL is not.

Cute, but if I also add

#define puts(s) printf("%s\n", "foo == NULL")

the program will always report "foo == NULL".

If you need to worry about someone maliciously changing the definition
of NULL behind your back, you've got bigger problems than we can solve
here.
 
C

CBFalconer

Kevin D. Quitt said:
They're guidelines, not hard rules. There are times not to
follow them, but it's almost never when you think it's useful.
These guidelines are for writing code that works because it's
supposed to, as opposed to code that works by accident.

In general, counting on the order of enums is not a good idea.
For your example, what happens if you turn left from EAST? What
if you want to expand what you have to include SE, NE, SW, NW,
and/or points in between? Your code has to change, and it has to
change everywhere.

Unlike Pascal, C enums are not truly enumerated types, but a handy
mechanism for generating integral constants and a type that
/should/ only hold those values. Consider:

enum foo {one = 2, two, three = 5, five = 4, six = 13} bar;

bar = three;

Now, what would you want bar-- to create? The antecedent
enumeration is two, with the value three. The entry value of bar
is 5, so the integral predecessor is 4, which is the value of
succesor enumeration. I see no solution, so I suggest the easiest
way to handle it is to forbid ++ and -- on enums.

Pascal enumerations have ordinals starting at zero, and are in
strict succession, so the above problem never can arise, and they
are well ordered. Each system has its plusses and minuses.
 
Y

yuu

Chris Torek said:
Modern C++ (which is quite different from the C++ I read about in
the original Stroustrup book) has quite a few different kinds of
casts, with subtly (or even blatantly) different meanings, but in
this particular case, the result of a "C-style cast" is a reference.

Are you saying this --(int)d is correct in C++? Interesting!

/* And to those who flamed me about decrementing that enum: I _do_ use
wrappers! And i _do_ check for -1. I'm not _that_ stupid. */
 
P

Peter Nilsson

[Cross posted and followups set to comp.lang.c++]


[--(int)d where d is an enum type object]
Are you saying this --(int)d is correct in C++?

No, he never said that.

I believe the behaviour is undefined if d's type is not compatible with int,
but questions on C++ are best answered in C++ groups...
 

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

Forum statistics

Threads
473,777
Messages
2,569,604
Members
45,216
Latest member
Best cryptoconsultant

Latest Threads

Top