bit-level logic operations on enums

Steven Schveighoffer schveiguy at yahoo.com
Fri Mar 1 13:27:25 PST 2013


On Fri, 01 Mar 2013 15:33:32 -0500, FG <home at 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


More information about the Digitalmars-d mailing list