Sure, that's why I use them too but that accepted value restriction is IMO not the root of your problem here.It has everything to do with enums. It tells the compiler the range of accepted values a variable has. This give you some power to avoid some kinds of programming errors.
An integer doesn't do this.
Not true.It's kludge that as you say, works with limitations. In C the initial enumerator always has the value zero and the value of each subsequent enumerator is one greater than the previous enumerator.
#include <stdio.h>
enum FLAGS
{
BIT0 = 1,
BIT1 = 2,
BIT2 = 4,
BIT3 = 8,
FLAGS_LAST
};
int main(void)
{
enum FLAGS status;
status = BIT1 | BIT3;
printf("status: %u\n", status);
printf("FLAGS_LAST = %u\n", FLAGS_LAST);
return 0;
}
Are you saying that your compiler will not issue those warnings if you have a default case?Obviously.
But I use the warning as thus:
Suppose I have 20 different functions that behave differently depending on the current state. I add a new state to the enum, and need to add the behavior for that state to the 20 functions as an additional case to a switch statement.
A quick compile will warn me if I missed updating one or more functions (happened twice today!).
The alternative is multiple debug cycles trying to figure out where I missed a case, and ensuring I exercise all 20 functions in all possible states.
The root of my problem is that I'd like my compiler to assist in any way that's technically possible, like telling me how many enum elements there are when I ask it. The compiler knows the answer. It just doesn't want me to know.Sure, that's why I use them too but that accepted value restriction is IMO not the root of your problem here.
I don't know. I'll check and report back later today.Are you saying that your compiler will not issue those warnings if you have a default case?
If so, then you have the problem of dealing with values outside set of enumerated values. If not, then I don't see the problem since it will still issue a warning if you don't have a case for the new label.
The only options are the enumerated values. There are no values outside.If so, then you have the problem of dealing with values outside set of enumerated values.
That's exactly what I said. "the value of each subsequent enumerator is one greater than the previous enumerator". I didn't say _LAST will always be the total number of enumerators.Not true.
That's the behavior ONLY if you choose to let the compiler assign ALL of the enumeration values.
In this case, FLAGS_LAST has the value 9 because it is not explicitly assigned a value, and so it is implicitly assigned a value one greater than the prior label.Code:#include <stdio.h> enum FLAGS { BIT0 = 1, BIT1 = 2, BIT2 = 4, BIT3 = 8, FLAGS_LAST }; int main(void) { enum FLAGS status; status = BIT1 | BIT3; printf("status: %u\n", status); printf("FLAGS_LAST = %u\n", FLAGS_LAST); return 0; }
Not as far as the compiler is concerned (unless your particular compiler is able to impose a much stricter set of limitations).The only options are the enumerated values. There are no values outside.
(Assuming the switched variable is type enum and not its integer interpretation. )
#include <stdio.h>
enum FLAGS
{
BIT0 = 1,
BIT1 = 2,
BIT2 = 4,
BIT3 = 8,
FLAGS_LAST
};
typedef enum FLAGS FLAGS;
void report(enum FLAGS status)
{
printf("status: %u\n", status);
switch(status)
{
case 0: printf("No bits set\n"); break;
case BIT1: printf("BIT 1 only bit set\n"); break;
case BIT2: printf("BIT 2 only bit set\n"); break;
case BIT3: printf("BIT 3 only bit set\n"); break;
case BIT1 | BIT3: printf("BIT 1 and 2 set\n"); break;
case 15: printf("ALL bits set\n"); break;
case FLAGS_LAST:
default:
printf("Something else.\n");
}
printf("\n");
}
int main(void)
{
enum FLAGS status;
printf("FLAGS_LAST = %u\n\n", FLAGS_LAST);
status = BIT1 | BIT3;
report(status);
status = BIT0 | BIT1 | BIT2 | BIT3;
report(status);
status = BIT0 * BIT1 * BIT2 * BIT3;
report(status);
status = 42;
report(status);
return 0;
}
FLAGS_LAST = 9
status: 10
BIT 1 and 2 set
status: 15
ALL bits set
status: 64
Something else.
status: 42
Something else.
The warning is still thrown even with a default case.Are you saying that your compiler will not issue those warnings if you have a default case?
You said "In C the initial enumerator always has the value zero and the value of each subsequent enumerator is one greater than the previous enumerator."That's exactly what I said. "the value of each subsequent enumerator is one greater than the previous enumerator". I didn't say _LAST will always be the total number of enumerators.
Ok. But bad programming practice.Not as far as the compiler is concerned (unless your particular compiler is able to impose a much stricter set of limitations).
Consider:
Which produces:Code:#include <stdio.h> enum FLAGS { BIT0 = 1, BIT1 = 2, BIT2 = 4, BIT3 = 8, FLAGS_LAST }; typedef enum FLAGS FLAGS; void report(enum FLAGS status) { printf("status: %u\n", status); switch(status) { case 0: printf("No bits set\n"); break; case BIT1: printf("BIT 1 only bit set\n"); break; case BIT2: printf("BIT 2 only bit set\n"); break; case BIT3: printf("BIT 3 only bit set\n"); break; case BIT1 | BIT3: printf("BIT 1 and 2 set\n"); break; case 15: printf("ALL bits set\n"); break; case FLAGS_LAST: default: printf("Something else.\n"); } printf("\n"); } int main(void) { enum FLAGS status; printf("FLAGS_LAST = %u\n\n", FLAGS_LAST); status = BIT1 | BIT3; report(status); status = BIT0 | BIT1 | BIT2 | BIT3; report(status); status = BIT0 * BIT1 * BIT2 * BIT3; report(status); status = 42; report(status); return 0; }
Using enums to give names to bit masks is very common, so if the resulting variables could only take on the values of individual masks and couldn't take on the values of combinations of them, that would be extremely limiting for these uses.Code:FLAGS_LAST = 9 status: 10 BIT 1 and 2 set status: 15 ALL bits set status: 64 Something else. status: 42 Something else.
You're right, I slipped, should have said default of zero. The default behaviour is what my _LAST kludge uses in my example code.You said "In C the initial enumerator always has the value zero and the value of each subsequent enumerator is one greater than the previous enumerator."
I provided a counter example in which both of these claims are false.
The initial enumerator did NOT have a value of zero -- it had a value of 1.
Each subsequent enumerator was NOT one greater than the previous one. The second enumerator had a value of 2, but the next one had a value of 4, and the one after that had a value of 8. Neither of these is one greater than the previous enumerator.
I think you should still have a default case, in which case (no pun intended) you can just have the last element flow into it.The warning is still thrown even with a default case.
Overall, this is good (and a little bit surprising).
But that means that, in order for the warnings to be useful for me while using @nsaspooks solution, I'd have to include a case in every switch statement for the n+1 enum element to suppress the warning.
In a real bitmasking application, you might have stuff like "case BIT3 | BIT2:", but you would NOT have a "case 12:" even though they are equivalent.Ok. But bad programming practice.
Either use the enum labels or don't. Mixing is, IMHO, to be avoided.
(BTW, your example is one of the reasons I like sets in Pascal.)
enum FLAGS
{
CENTERED_X = 1,
CENTERED_Y = 2,
COLORED = 4,
ALPHA = 8,
};
typedef enum FLAGS FLAGS;
enum ADJUSTMENTS
{
ALPHA, BETA, GAMMA
};
typedef enum ADJUSTMENTS ADJUSTMENTS;
Something I wouldn't do. Rather:"case BIT3 | BIT2" ...
switch (x)
{
case BIT3:
case BIT2:
do something...
break;
...
}
Except that that isn't the same logic at all.Something I wouldn't do. Rather:
C:switch (x) { case BIT3: case BIT2: do something... break; ... }
Your argument really is a straw man. In your example, it will also not match if BIT2 and BIT3 are set, but also BIT 1 and/or BIT4.Except that that isn't the same logic at all.
Your code requires that x be either EXACTLY equal to BIT2 or exactly equal to BIT3. If BOTH bits are set, your code will not do something.
The code you are trying to match does that case when X is exactly equal to having BOTH of those bits set. If only one of them is set, it won't match.
I know it will not match if one of the other bits is also set -- that is why I very explicitly used the term "exactly".Your argument really is a straw man. In your example, it will also not match if BIT2 and BIT3 are set, but also BIT 1 and/or BIT4.
Using the number instead of the label is extremely poor style. Also, in standard C, b'1100' is not defined. That is a compiler extension that risks locking the code to that compiler, which may or may not be undesirable.If you really mean BIT2 and BIT3 set exclusively (which is not apparent from the style or language), then just use the number (or label) for 12 (or, even better, b'1100'!). Otherwise, you're asking for trouble.
Please show me an actual real-world example of someone who programs like this. I'll know not to hire him.
#include <stdio.h>
enum VALVES
{
OXYGEN = 1<<0,
NITROGEN = 1<<1,
ARGON = 1<<2,
CO2 = 1<<3,
METHANE = 1<<4,
HELIUM = 1<<5,
};
typedef enum VALVES VALVES;
#define NOBLE (HELIUM + ARGON)
void noble_content(VALVES mix)
{
switch (mix & NOBLE)
{
case ARGON:
case HELIUM:
printf("Mixture contains one noble gas\n\n");
break;
case ARGON + HELIUM:
printf("Mixture contains two noble gases\n\n");
break;
default:
printf("Mixture doesn't contain either noble gas.\n\n");
}
}
int main(void)
{
enum VALVES mixture;
mixture = METHANE + ARGON + OXYGEN;
noble_content(mixture);
mixture = METHANE + CO2 + OXYGEN;
noble_content(mixture);
mixture += HELIUM;
noble_content(mixture);
mixture += ARGON;
noble_content(mixture);
return 0;
}