Jump to page: 1 2
Thread overview
using enums for flags
Jan 25, 2012
Trass3r
Jan 25, 2012
Simen Kjærås
Jan 25, 2012
Trass3r
Jan 25, 2012
Simen Kjærås
Jan 25, 2012
Trass3r
Jan 26, 2012
Simen Kjærås
Jan 26, 2012
Jonathan M Davis
Jan 26, 2012
Trass3r
Jan 26, 2012
Jonathan M Davis
Jan 26, 2012
Mantis
Jan 26, 2012
Trass3r
Jan 26, 2012
bearophile
Jan 26, 2012
Marco Leise
Jan 26, 2012
foobar
January 25, 2012
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
January 25, 2012
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.

January 25, 2012
>> 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.
January 25, 2012
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.
January 25, 2012
> 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?
January 26, 2012
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
January 26, 2012
> 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.
January 26, 2012
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
January 26, 2012
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.
January 26, 2012
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.
« First   ‹ Prev
1 2