www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Re: Bug 3999 and 4261

Steven Schveighoffer:

 enum myBits
     flag1 = 1;
     flag2 = 2;
     flag3 = 4;
 void fn(int flags);
 fn(myBits.flag1 | myBits.flag2);
 That was the one case where I really like the implicit conversion.

Thank you for that important use case. enum is used for three different things: compile-time constants, a sequence of separated symbols, and a group of flags that may be grouped. In C# there is the [Flags] attribute that helps a bit in the creation of such flags, and documents that an enum is indeed a flags set. Recently I have proposed to add to Phobos something similar to a std.bitmanip.bitfields that builds a flag set (the powers of two are set automatically, you just give the flag names. It may also add a first dummy flag with value zero). Probably it's not too much hard to do. The problem is that such (hypotetically named) std.bitmanip.flagset defines what the language/compiler sees as a normal enum, so it has to share its features with normal enums. In C++0x you don't have this problem, because you use "enum" to define flags and "enum class" to define the strongly typed sequence of symbols. Your fn() function doesn't truly accept any int, it accepts only a bitwise combination of members of myBits. A more strict type system may enforce this. This is feasible in D if std.bitmanip.flagset returns a struct instead of an enum. This struct contains just an int and methods to perform bitwise operations that return a struct of the same type. So the signature becomes void fn(MyBits flags). This is an idea: struct MyBits { private alias size_t T; static assert(is(T == ubyte) || is(T == ushort) || is(T == uint) || is(T == ulong) || is(T == size_t)); private T _bits; public enum nothing = MyBits(0); private enum uint nflags = 3; // flag1, flag2, flag3 static assert(nflags <= T.sizeof * 8); static if (nflags == (T.sizeof * 8)) private enum T full = T.max; // because ((1UL << 64) - 1) != ulong.max else private enum T full = ((cast(T)1) << nflags) - 1; public enum flag1 = MyBits((cast(T)1) << 0); public enum flag2 = MyBits((cast(T)1) << 1); public enum flag3 = MyBits((cast(T)1) << 2); this(MyBits other) { this._bits = other._bits; } this(T value) { this._bits = value; } invariant() { assert(!(this._bits & ~full)); } MyBits opUnary(string Op="~")() { return MyBits(~this._bits & full); } MyBits opBinary(string op)(MyBits other) if (op == "|" || op == "&") { mixin("return MyBits(this._bits " ~ op ~" other._bits);"); } } //-------------------------- import std.stdio: writeln; void fn(MyBits flags) { writeln(flags._bits); } void main() { fn(MyBits.flag1 | MyBits.flag2); fn(MyBits.flag1 & MyBits.flag2); auto f = MyBits.flag1; assert(f == MyBits.flag1); writeln((1UL << 64) - 1); } Given the names "flag1", "flag2", "flag3" (and the optional type T), std.bitmanip.flagset generates a struct like MyBits (plus few more missing parts). ------------------------- Don:
 OTOH if each member has an explicitly defined value, it's reasonable to
 perform logical operations on it.

Thank you for your usually wise point of view. Bye, bearophile
Sep 02 2010