www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - using enums for flags

reply Trass3r <un known.com> writes:
So I was just reading  
http://stackoverflow.com/questions/1448396/how-to-use-enums-as-flags-in-c

And did a quick test:

enum STC
{
	A = 0x1,
	B = 0x2,
	C = 0x4
}
enum FOO
{
	F = 0x8,
	G = 0x10
}

void main()
{
	STC s = STC.A | STC.C;
	STC s2 = s & STC.A;
	auto s2_2 = s & STC.A;
	static assert(is(typeof(s2_2) == STC));

	static assert(!__traits(compiles, { if (s & FOO.F) {} }));       //  
fails. that one's hard to track down
	static assert(!__traits(compiles, { auto s3 = s & FOO.F; }));    //  
fails, cause implicitly converts to int
	static assert(!__traits(compiles, { STC s4 = s & FOO.F; }));
	static assert(!__traits(compiles, { FOO s5 = s & FOO.F; }));

	static assert(!__traits(compiles, { STC t = STC.A | FOO.F; }));
	static assert(!__traits(compiles, { auto t = STC.A | FOO.F; })); // fails

	static assert(!__traits(compiles, { if (s & STC.B == 0) {} }));  //  
works, but error not gagged
	static assert(!__traits(compiles, { if (s && STC.B) {} }));      // fails
}

Does it really make sense to allow bitwise operations on different enums?
There should at least be some way to get this straight without having to  
resort to a heap of code like in C++:  
http://www.artima.com/cppsource/safelabels.html
Jan 24 2012
next sibling parent reply =?utf-8?Q?Simen_Kj=C3=A6r=C3=A5s?= <simen.kjaras gmail.com> writes:
On Wed, 25 Jan 2012 03:22:03 +0100, Trass3r <un known.com> wrote:

 Does it really make sense to allow bitwise operations on different enums?

Maybe. Certainly sometimes, but those could just as easily use casts.
 There should at least be some way to get this straight without having to  
 resort to a heap of code like in C++:  
 http://www.artima.com/cppsource/safelabels.html

Well, it may be a heap of code, but it's a lot less than what you linked. Example attached. It's WTFPL or freer, so feel free to use it as you wish.
Jan 25 2012
parent reply Trass3r <un known.com> writes:
 Does it really make sense to allow bitwise operations on different  
 enums?

Maybe. Certainly sometimes

Examples please.
 but those could just as easily use casts.

Seconded. I generally don't see any merit in letting enums *implicitly* convert to their base type.
Jan 25 2012
parent reply =?utf-8?Q?Simen_Kj=C3=A6r=C3=A5s?= <simen.kjaras gmail.com> writes:
On Wed, 25 Jan 2012 22:47:49 +0100, Trass3r <un known.com> wrote:

 Does it really make sense to allow bitwise operations on different  
 enums?

Maybe. Certainly sometimes

Examples please.

In the codebase I have to work with, having the same enum specified in different places is rather common. Yeah, I hate it. This means I might have a filter defined using one enum, and the value to filter being a different type with the same values.
 but those could just as easily use casts.

Seconded. I generally don't see any merit in letting enums *implicitly* convert to their base type.

Jan 25 2012
parent reply Trass3r <un known.com> writes:
 In the codebase I have to work with, having the same enum specified in
 different places is rather common. Yeah, I hate it. This means I might
 have a filter defined using one enum, and the value to filter being a
 different type with the same values.

Why don't you fix it then?
Jan 25 2012
parent =?utf-8?Q?Simen_Kj=C3=A6r=C3=A5s?= <simen.kjaras gmail.com> writes:
On Thu, 26 Jan 2012 00:49:58 +0100, Trass3r <un known.com> wrote:

 In the codebase I have to work with, having the same enum specified in
 different places is rather common. Yeah, I hate it. This means I might
 have a filter defined using one enum, and the value to filter being a
 different type with the same values.

Why don't you fix it then?

I'm not allowed to. The project I'm on has its tasks, and fixing things in other projects is not one of them, especially when such an easy workaround exists.
Jan 26 2012
prev sibling next sibling parent Era Scarecrow <rtcvb32 yahoo.com> writes:
 On Wed, 25 Jan 2012 03:22:03 +0100, Trass3r <un known.com>
 wrote:
 Does it really make sense to allow bitwise operations

on different enums? =20 Maybe. Certainly sometimes, but those could just as easily use casts. =20
 There should at least be some way to get this straight

without having to=A0=20
 resort to a heap of code like in C++:=A0=20


To my understanding, converting an int to an enum (In C++ or D) is basical= ly illegal. You'd have to do force casting to get it to work, which isn't s= afe. Enums seem better suited for a long range of codes that don't have spe= cific bits for on/off If it hasn't been suggested yet, I am currently using a block of personal = code to use enums as flags. I'll give you a stripped down version of the st= ructure, if everyone says 'yay' or perhaps Walter feels he wants it, we can= propose maybe get it in phobos. How it works, is internally it will store the flag as an int (or whatever = type S is), then you can do checks and it will convert the enum to an int a= nd do appropriate bit casts. Haven't tried to consider how to get a array o= f separate flags. ///T of type ENUM, and S of an integral. struct HandleFlags(T, S) { =09S state;=09///Holds state. =09alias T T_Enum; =09this(T[] setFlags...); =09///Returns true/false if a specific ENUM flag has been set. =09bool check(T flag); =09/** =09Checks if a flag has been set, returning that ENUM, otherwise returning = the Else flag. =09*/ =09T checkElse(T flag, T Else); =09 =09///Sets specific flag on or off. Default sets flag =09void setFlag(T flag, bool on_off =3D true); =09 =09///reverses the state of a specific flag. =09void flipFlag(T flag); } Usage is like this. =09enum ETEST {zero =3D 0, one =3D 1, two =3D 2, four =3D 4} =09HandleFlags!(ETEST, int) ftest; =09HandleFlags!(ETEST, int) ftest1(ETEST.one); //1 =09HandleFlags!(ETEST, int) ftest2(ETEST.two, ETEST.four); //6
Jan 25 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, January 25, 2012 03:22:03 Trass3r wrote:
 So I was just reading
 http://stackoverflow.com/questions/1448396/how-to-use-enums-as-flags-in-c
 
 And did a quick test:
 
 enum STC
 {
 	A = 0x1,
 	B = 0x2,
 	C = 0x4
 }
 enum FOO
 {
 	F = 0x8,
 	G = 0x10
 }
 
 void main()
 {
 	STC s = STC.A | STC.C;
 	STC s2 = s & STC.A;
 	auto s2_2 = s & STC.A;
 	static assert(is(typeof(s2_2) == STC));
 
 	static assert(!__traits(compiles, { if (s & FOO.F) {} }));       //
 fails. that one's hard to track down
 	static assert(!__traits(compiles, { auto s3 = s & FOO.F; }));    //
 fails, cause implicitly converts to int
 	static assert(!__traits(compiles, { STC s4 = s & FOO.F; }));
 	static assert(!__traits(compiles, { FOO s5 = s & FOO.F; }));
 
 	static assert(!__traits(compiles, { STC t = STC.A | FOO.F; }));
 	static assert(!__traits(compiles, { auto t = STC.A | FOO.F; })); // fails
 
 	static assert(!__traits(compiles, { if (s & STC.B == 0) {} }));  //
 works, but error not gagged
 	static assert(!__traits(compiles, { if (s && STC.B) {} }));      // fails
 }
 
 Does it really make sense to allow bitwise operations on different enums?
 There should at least be some way to get this straight without having to
 resort to a heap of code like in C++:
 http://www.artima.com/cppsource/safelabels.html

I think that it makes sense to use enums as flags, but I do _not_ think that it makes sense to use an enum as the type of the variable _holding_ the flags. STC var = STC.A & STC.B; is an abimination IMHO. adding another enum to the list and doing something like STC var = STC.A & FOO.F; just makes it worse. It should be something like uint var = STC.A & FOO.F; instead. Now anding two different enums like that is still a bit weird, but I don't know that it should really be illegal. It's assigning anded enums to an enum that I want to see die. I'd _love_ it if that were illegal. For instance, std.socket uses flag enums, which is fine, but in some places it uses them as the type of function parameters, which is _not_ a good idea IMHO. Whenever I think about it, I keep meaning to go and fix it, but I never get around to it. - Jonathan M Davis
Jan 25 2012
parent reply Trass3r <un known.com> writes:
 I think that it makes sense to use enums as flags, but I do _not_ think  
 that it makes sense to use an enum as the type of the variable _holding_  
 the flags.

 STC var = STC.A & STC.B;

We could easily introduce flags enum or whatever to make it more clear like in C#.
 just makes it worse. It should be something like

 uint var = STC.A & FOO.F;

To me it doesn't make any sense at all to allow bitwise operations on different *named* enums. I also don't see why you would really need implicit conversion to the base type.
 For instance, std.socket uses flag enums, which is fine, but in some  
 places it uses them as the type of function parameters, which is _not_ a  
 good idea IMHO.

You really think int is any better?? No type safety at all.
Jan 25 2012
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday, January 26, 2012 01:17:51 Trass3r wrote:
 I think that it makes sense to use enums as flags, but I do _not_ think
 that it makes sense to use an enum as the type of the variable _holding_
 the flags.
 
 STC var = STC.A & STC.B;

We could easily introduce flags enum or whatever to make it more clear like in C#.
 just makes it worse. It should be something like
 
 uint var = STC.A & FOO.F;

To me it doesn't make any sense at all to allow bitwise operations on different *named* enums. I also don't see why you would really need implicit conversion to the base type.
 For instance, std.socket uses flag enums, which is fine, but in some
 places it uses them as the type of function parameters, which is _not_ a
 good idea IMHO.

You really think int is any better?? No type safety at all.

What type safety? You're dealing with a uint (or ushort or ulong) with a bunch of specific bits set which represent flags. What other type would you want? You could typedef it I suppose (well, use the TypeDef library type when it's merged in anyway), but you're dealing with a fixed number of bits, which is exactly what an unsigned integer is. It's just a question of which are on and which are off. That's certainly not what _enum_ is for. It's a fixed set of values. And that's my beef with using it as the result of &ing flags. The result isn't one of those flags, so it has no business being an enum. - Jonathan M Davis
Jan 25 2012
prev sibling parent reply Mantis <mail.mantis.88 gmail.com> writes:
26.01.2012 2:40, Jonathan M Davis пишет:
 What type safety? You're dealing with a uint (or ushort or ulong) with a bunch
 of specific bits set which represent flags. What other type would you want? You
 could typedef it I suppose (well, use the TypeDef library type when it's
 merged in anyway), but you're dealing with a fixed number of bits, which is
 exactly what an unsigned integer is. It's just a question of which are on and
 which are off. That's certainly not what _enum_ is for. It's a fixed set of
 values.

 And that's my beef with using it as the result of&ing flags. The result isn't
 one of those flags, so it has no business being an enum.

 -  Jonathan M Davis

I agree, enum variable should only contain one of the enumerated values. Here's an example how current way may lead to unexpected result: enum Foo { A = 1, B } void bar( Foo foo ) { final switch( foo ) { case Foo.A: writeln( "A" ); return; case Foo.B: writeln( "B" ); return; } writeln( "Unreachable branch" ); } int main() { bar( Foo.A | Foo.B ); return 0; } It is intuitive to assume that the final switch will always hit one of it's branches, yet this doesn't work here.
Jan 25 2012
parent reply "Trass3r" <un known.com> writes:
 I agree, enum variable should only contain one of the 
 enumerated values. Here's an example how current way may lead 
 to unexpected result:

 enum Foo { A = 1, B }

 void bar( Foo foo ) {
    final switch( foo ) {
        case Foo.A:
            writeln( "A" );
            return;
        case Foo.B:
            writeln( "B" );
            return;
    }
    writeln( "Unreachable branch" );
 }

 int main() {
    bar( Foo.A | Foo.B );
    return 0;
 }

 It is intuitive to assume that the final switch will always hit 
 one of it's branches, yet this doesn't work here.

Valid example. As I said one could introduce something like flags but I guess a library solution is preferred. I still wonder though if implicit conversion to the basetype has any merit.
Jan 26 2012
parent bearophile <bearophileHUGS lycos.com> writes:
Trass3r:

 As I said one could introduce something like  flags but I guess a 
 library solution is preferred.
 I still wonder though if implicit conversion to the basetype has 
 any merit.

Those are important topics. D must offer a solution that is both safer and more handy than the current one. Bye, bearophile
Jan 26 2012
prev sibling next sibling parent Era Scarecrow <rtcvb32 yahoo.com> writes:
 On Wednesday, January 25, 2012 03:22:03 Trass3r wrote:
 So I was just reading
 http://stackoverflow.com/questions/1448396/how-to-use-enums-as-flags-in-c
 
 And did a quick test:

I think that it makes sense to use enums as flags, but I do _not_ think that it makes sense to use an enum as the type of the variable _holding_ the flags. STC var = STC.A & STC.B; is an abimination IMHO. adding another enum to the list and doing something like STC var = STC.A & FOO.F; just makes it worse. It should be something like uint var = STC.A & FOO.F; instead. Now anding two different enums like that is still a bit weird, but I don't know that it should really be illegal. It's assigning anded enums to an enum that I want to see die. I'd _love_ it if that were illegal. For instance, std.socket uses flag enums, which is fine, but in some places it uses them as the type of function parameters, which is _not_ a good idea IMHO. Whenever I think about it, I keep meaning to go and fix it, but I never get around to it.

Indeed. enum should be only a single value, be it a number like 42, or a binary bit 1<<42. The type checking enforces it to be a valid value. You can easily turn a enum into an int, but the other direction as said before, is not so easy. I started on C++ recently and came across that problem, and came across this solution (ported shortly after to D). Here's my unittests. (they pass) unittest{ enum ETEST { none = 0, one = 1, two = 2, four = 4 } alias ETEST.none none; alias ETEST.one one; alias ETEST.two two; alias ETEST.four four; HandleFlags!(ETEST, int) ftest; //test all flags off. Uses int checks. assert(ftest.state == 0); //start empty. //set 2 flag ftest.setFlag(two, true); assert(ftest.state == 2); //set 4, should now be 6 ftest.setFlag(four, true); assert(ftest.state == 6); //turn off 2 bit ftest.setFlag(two, false); assert(ftest.state == 4); //flip 1 ftest.flipFlag(one); //4+1 assert(ftest.state == 5); //flip 1 again. ftest.flipFlag(one); //4+0 assert(ftest.state == 4); //check truth/else ETEST x = ftest.checkElse(four, none); assert(x == four); x = ftest.checkElse(one, none); assert(x == none); assert(ftest.check(four) == true); assert(ftest.check(two) == false); }
Jan 25 2012
prev sibling parent reply "Marco Leise" <Marco.Leise gmx.de> writes:
Delphi: http://delphi.about.com/od/beginners/a/delphi_set_type.htm |  
Scroll to: "Sets with Enumerations"
Sets use the smallest integer type that can hold enough bits for the  
number of elements in an enum. So up to 8 enum flags use a byte for  
example. TDaySet in the example code would also be 1 byte in size. As the  
syntax suggests they don't implicitly convert forth or back to integers.
Jan 25 2012
parent "foobar" <foo bar.com> writes:
On Thursday, 26 January 2012 at 01:44:23 UTC, Marco Leise wrote:
 Delphi: 
 http://delphi.about.com/od/beginners/a/delphi_set_type.htm | 
 Scroll to: "Sets with Enumerations"
 Sets use the smallest integer type that can hold enough bits 
 for the number of elements in an enum. So up to 8 enum flags 
 use a byte for example. TDaySet in the example code would also 
 be 1 byte in size. As the syntax suggests they don't implicitly 
 convert forth or back to integers.

vote += MAX_INT for the above suggestion which btw is also the solution in Java 5.0 which provides an EnumSet type. At the moment eums are completely broken in D and IMO they are even worse that the unsafe C counterparts. C# is a lousy example here since its design is only very slightly better than the original C semantics. I suggest looking into the Java implementation of Enum and EnumSet types which provides both type safety and convenience.
Jan 26 2012