Thread overview | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
March 01, 2013 bit-level logic operations on enums | ||||
---|---|---|---|---|
| ||||
Attachments:
| Greetings Currently DMD allows me to bit-or two enum variables, but not bit-and. For example in the following code, DMD is OK with line 6, but gives an error for line 9. I do not see any logic why DMD does that. Is it a bug or is it intended. BTW if I declare all a,b,c and d as "enum BING_e", then DMD gives errors for both line 6 and 9. Regards - Puneet enum BING_e: byte {NONE_BING = 0b00, FOO_BING = 0b01, // 1 BAR_BING = 0b10, BOTH_BING = 0b11} // 2 void main() { // 3 BING_e a = BING_e.FOO_BING; // 4 BING_e b = BING_e.BAR_BING; // 5 BING_e c = a | b; // 6 import std.stdio; // 7 writeln(c); // 8 BING_e d = a & b; // 9 import std.stdio; // 10 writeln(d); // 11 } // 12 |
March 01, 2013 Re: bit-level logic operations on enums | ||||
---|---|---|---|---|
| ||||
Posted in reply to d coder | On 2013-03-01 13:56, d coder wrote:
> Currently DMD allows me to bit-or two enum variables, but not bit-and. For
> example in the following code, DMD is OK with line 6, but gives an error
> for line 9. I do not see any logic why DMD does that. Is it a bug or is it
> intended.
Strange. | and ^ work but & fails. The promotion from byte to int is unnecessary in any of those 3 ops. It works correctly for non-enums:
byte x, y, z = 1;
z = x | y; z = x ^ y; z = x & y; z = x >> y; // all OK
z = x << y; z = x + y; z = x * y; z = x / y; // can't convert int to byte
Not sure why byte enum would be handled any differently than simple byte.
|
March 01, 2013 Re: bit-level logic operations on enums | ||||
---|---|---|---|---|
| ||||
Posted in reply to d coder | On Friday, 1 March 2013 at 12:57:14 UTC, d coder wrote: > Greetings > > Currently DMD allows me to bit-or two enum variables, but not bit-and. For example in the following code, DMD is OK with line 6, but gives an error for line 9. I do not see any logic why DMD does that. Is it a bug or is it intended. > > BTW if I declare all a,b,c and d as "enum BING_e", then DMD gives errors for both line 6 and 9. > > enum BING_e: byte {NONE_BING = 0b00, FOO_BING = 0b01, // 1 > BAR_BING = 0b10, BOTH_BING = 0b11} // 2 > void main() { // 3 > BING_e a = BING_e.FOO_BING; // 4 > BING_e b = BING_e.BAR_BING; // 5 > BING_e c = a | b; // 6 > import std.stdio; // 7 > writeln(c); // 8 > BING_e d = a & b; // 9 > import std.stdio; // 10 > writeln(d); // 11 > } // 12 To do the binary operators it converts the flags to numbers, however you have to cast it for it to become a enum again, however this is unsafe as you could accidentally hold a value that has no name; //with your code BING_e c = cast(BING_e) (a | 0x10); //suppose to be 0b10 If you want to work with flags I have a implementation that I think is very good and simple. Note: If you have a flag that covers multiple bits like BOTH_BING, it must match ALL the bits to qualify. https://github.com/rtcvb32/Side-Projects/blob/master/flags.d |
March 01, 2013 Re: bit-level logic operations on enums | ||||
---|---|---|---|---|
| ||||
Posted in reply to Era Scarecrow | On Friday, 1 March 2013 at 13:41:27 UTC, Era Scarecrow wrote:
> To do the binary operators it converts the flags to numbers,
s/flags/enum(s)
|
March 01, 2013 Re: bit-level logic operations on enums | ||||
---|---|---|---|---|
| ||||
Posted in reply to d coder | On Fri, 01 Mar 2013 07:56:21 -0500, d coder <dlang.coder@gmail.com> wrote:
> Greetings
>
> Currently DMD allows me to bit-or two enum variables, but not bit-and. For
> example in the following code, DMD is OK with line 6, but gives an error
> for line 9. I do not see any logic why DMD does that. Is it a bug or is it
> intended.
It is a bug that line 6 is not an error:
BING_e c = a | b; // OK (bug!)
writeln(typeof(a | b).stringof); // int
BING_e c2 = 0b11; // ERROR, cannot assign int value to enum BING_e (should be the message for 2 lines above as well)
D does not (or at least should not) allow assignments of integers back to enumeration types. You have to cast. Any time an enumeration is involved in a math operation, it becomes an integer (as my code shows).
The "correct" way to do this is not to store the result of an operation with flags as an enum, but rather as a uint (or ushort or ubyte). Then check flags using the enum.
In other words, c and d in your code should be ubytes, not BING_e.
The largest drawback to this approach is that typechecking for enums is gone when calling functions -- your function has to take a uint, not the enum, and random bits, other enum bits, etc can be passed in. Of course, technically, this is possible anyway, and often times certain flag sets are invalid. Impossible for the compiler to verify these things for you, you might want to use an in clause for the function to verify the bitset is correct.
-Steve
|
March 01, 2013 Re: bit-level logic operations on enums | ||||
---|---|---|---|---|
| ||||
Posted in reply to FG | On Fri, 01 Mar 2013 08:38:53 -0500, FG <home@fgda.pl> wrote:
> Not sure why byte enum would be handled any differently than simple byte.
An enum is guaranteed by the compiler to be one of the specified values. The simple accounting kept by the compiler to prove whether one type will fit in another doesn't include enums, that is just prohibited, even if it seems like it should result in a valid enum value.
-Steve
|
March 01, 2013 Re: bit-level logic operations on enums | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On 2013-03-01 20:13, Steven Schveighoffer wrote:
> D does not (or at least should not) allow assignments of integers back to
> enumeration types. You have to cast.
or use normal int enum instead of byte enum, if you have the space for it.
|
March 01, 2013 Re: bit-level logic operations on enums | ||||
---|---|---|---|---|
| ||||
Posted in reply to FG | On Fri, 01 Mar 2013 14:32:11 -0500, FG <home@fgda.pl> wrote:
> On 2013-03-01 20:13, Steven Schveighoffer wrote:
>> D does not (or at least should not) allow assignments of integers back to
>> enumeration types. You have to cast.
>
> or use normal int enum instead of byte enum, if you have the space for it.
It still fails if int is the enum base. The issue is not whether the bit pattern will fit in the base type, the issue is assigning an enum from int.
-Steve
|
March 01, 2013 Re: bit-level logic operations on enums | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On 2013-03-01 20:56, Steven Schveighoffer wrote:
>> or use normal int enum instead of byte enum, if you have the space for it.
>
> It still fails if int is the enum base. The issue is not whether the bit
> pattern will fit in the base type, the issue is assigning an enum from int.
What fails? Are we talking something compiler-version-specific here?
enum BING_e { ... } ... // rest the same as in original post
BING_e c = a & b; // this works for me
BING_e d = a & 1; // fails, and it should fail
|
March 01, 2013 Re: bit-level logic operations on enums | ||||
---|---|---|---|---|
| ||||
Posted in reply to FG | On Fri, 01 Mar 2013 15:33:32 -0500, FG <home@fgda.pl> wrote:
> On 2013-03-01 20:56, Steven Schveighoffer wrote:
>>> or use normal int enum instead of byte enum, if you have the space for it.
>>
>> It still fails if int is the enum base. The issue is not whether the bit
>> pattern will fit in the base type, the issue is assigning an enum from int.
>
> What fails? Are we talking something compiler-version-specific here?
>
> enum BING_e { ... } ... // rest the same as in original post
> BING_e c = a & b; // this works for me
> BING_e d = a & 1; // fails, and it should fail
>
My test code was incorrect. I had commented out the line that assigned c, and replaced with an assignment to an int (to test something else).
In fact, any math on two or more enums is assignable back to an enum (for int or larger). I don't understand why this should be valid for int, but not for any other type.
These make no sense, even in terms of bitfield flags, but the compiler accepts them:
a << b;
a * b;
According to the spec, enums are integer promoted based on their base-type. But int isn't promoted, so apparently that enum is left alone.
Then, when determining the type of an enum operation, if the two operands are of the same enum type (meaning, they haven't gone through any integer promotion), the result is the same enum type.
So this unintuitively results in:
enum A : int { a }
enum B : byte { b }
A a;
B b;
writeln(typeof(a | a).stringof); // => A
writeln(typeof(b | b).stringof); // => int (byte is promoted to int)
writeln(typeof(a | b).stringof); // => int
writeln(typeof(a | 1).stringof); // => int
writeln(typeof(b | 1).stringof); // => int
So I guess it works according to the spec, but the spec makes no sense to me. I don't understand why int or uint (or larger) can be used as bitfields, but smaller types cannot. Nor do I understand why arbitrary math operations between enums are valid (can't think of a use case), while arbitrary math between enum and int is not.
I would push for one of 4 behaviors, which make some sense to me:
1. Math operations between two enum values of the same type ALWAYS result in the same enum type regardless of base type (like int-based enums)
2. Math operations between two enum values of the same type ALWAYS result in the base type (full-scale enforcement of "enum only contains values from the identified list")
3. Operations between two enums or between an enum and an int, that result at compile-time in a valid member of the enum result in the enum type. Otherwise, the operation is converted to the base type.
4. Enums can be assigned any value from its base type, or any value implicitly convertible to that base type.
As it stands now, the compiler makes a half-assed attempt to prevent invalid enum values, but fails miserably in some cases, and is overzealous in others, which is in fact worse than either one alone! At this point, you can't say anything about enums that is always valid except the manifest-constant usage of them.
TDPL is puzzlingly silent on enum math.
-Steve
|
Copyright © 1999-2021 by the D Language Foundation