EnumToFlags

ag0aep6g via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Thu Jun 30 03:11:24 PDT 2016


On 06/30/2016 04:39 AM, JS wrote:
> struct EnumToFlags(alias E)
> {
>      import std.traits, std.conv, std.string, std.algorithm, std.array;
>
>      static if (E.max < 8)
>          alias vtype = ubyte;

Convention says: Capitalize user-defined type names. So it should be 
"Vtype" or maybe "VType" instead of "vtype".

>      else static if (E.max < 16)
>          alias vtype = ushort;
>      else static if (E.max < 32)
>          alias vtype = uint;
>      else static if (E.max < 64)
>          alias vtype = ulong;
>      else static if (E.max < 128)
>          alias vtype = ucent;

There is no ucent type. The keyword is reserved, but there is no actual 
type.

>      else static assert("Cannot store more than 128 flags in an enum.");
>
>      vtype value;
>
>
>      template opDispatch(string Name)
>      {
>          enum opDispatch = 1 << __traits(getMember, E, Name);

`1` needs to be `1L` here, or maybe `1UL`. Can't shift more than 31 bits 
otherwise, because `1` is an int. Same throughout.

Note that you're shifting by the enum member's value here.

>      }
>
>
>      auto opAssign(vtype e)
>      {
>          value = e;
>      }

In my opinion, a void return type would be clearer here.

>
>      bool opEquals(typeof(this) e)
>      {
>          if (e is this) return true;
>          return (value == e.value);
>      }

opEquals should be const. The parentheses around the return value are 
pointless.

Does this opEquals do anything different from the default one? As far as 
I see, `e is this` does the same as `value == e.value` and the default 
equality would do the same, too.

>
>      auto opBinary(string op)(typeof(this) e)
>      {
>          auto x = typeof(this)();
>          mixin("x.value = this.value "~op~" e.value;");
>          return x;
>      }
>
>      auto opUnary(string op)()
>      {
>          auto x = typeof(this)();
>          mixin("x.value = "~op~"this.value;");
>          return x;
>      }
>
>      auto opCast()
>      {
>          return value;
>      }

When is this opCast going to be called?

>
>      auto toString()
>      {
>          string s;
>          foreach(i, e; EnumMembers!E)
>          {
>              if (((1 << i) & value) == 1 << i)
>                  s ~= to!string(e) ~ " | ";
>          }
>          if (s.length > 0)
>              s = s[0..$-3];
>          return s;
>      }
>
>
>      this(string from)
>      {
>          char uOp = ' ';
>          char Op = '=';

Convention says: Don't capitalize variable names. So it should be "op" 
instead of "Op".

>          char nOp = '=';
>          int previdx = 0;
>          value = 0;
>          for(int i = 0; i < from.length; i++)

There's a subtle problem here with i's type. from.length is a size_t, 
i.e. ulong in a 64 bit program. So it may be larger than int.max. When 
that happens, you've an infinite loop here, as i can never reach 
from.length. Solution: use size_t for i and previdx.

Also, foreach is nicer:

     foreach (i; 0 .. from.length)

>          {
>
>              nOp = from[i];
>              if (nOp == '!' || nOp == '~')
>              {
>                  uOp = nOp;
>                  previdx = i + 1;
>                  continue;
>              }
>
>              if (nOp == '&' || nOp == '^' || nOp == '|' || nOp == '+' ||
> nOp == '-' || i == from.length-1)
>              {
>
>                  auto v = from[previdx..(i + ((i == from.length-1) ? 1 :
> 0))].strip();
>                  vtype x = 0;
>                  foreach(j, e; EnumMembers!E)
>                      if (to!string(e) == v)
>                          x = cast(vtype)(1 << j);

Here you're shifting by the enum member's index. In opDispatch you shift 
by its value.

That difference leads to this:

     enum E {a = 0, b = 2}
     alias F = EnumToFlags!E;
     assert(F("a") is F.a); /* passes */
     assert(F("b") is F.b); /* fails */

>
>                  if (uOp == '!')
>                      x = !x;
>                  if (uOp == '~')
>                      x = ~x;
>
>                  switch(Op)
>                  {
>                      case '&': value = cast(vtype)(value & x); break;
>                      case '|': value = cast(vtype)(value | x); break;
>                      case '+': value = cast(vtype)(value + x); break;
>                      case '-': value = cast(vtype)(value - x); break;
>                      case '^': value = cast(vtype)(value ^ x); break;
>                      case '=': value = cast(vtype)(x); break;
>                      default: assert("Invalid EnumFlags operation
> `"~Op~"`.");
>                  }
>
>                  previdx = i + 1;
>                  Op = nOp;
>                  uOp = ' ';
>              }
>          }
>
>      }
>
>      alias value this;
> };

No semicolon after struct declarations in D.

You forgot to include some tests or a usage example. It's not obvious to 
me what EnumToFlags is supposed to accomplish or how to use it.

>
> Possibly the to and from string stuff can be improved? Simply apply to a
> pre-existing enum.

I'm not sure what the purpose of all this is, but it seems a bit 
convoluted to me.

Conversion from string seems pointless. In my opinion, this:

     alias F = EnumToFlags!E;
     auto f = F("foo | bar");

isn't better than this:

     alias F = EnumToFlags!E;
     auto f = F.foo | F.bar;

For conversion to string I can't think of a use case other than 
debugging. And for debugging a free function would be ok, too, I think.

Without the conversions from/to string, the struct just forwards to the 
value member, and one could just use the integer directly.

Have you considered simply generating a new enum with power-of-two 
values? For example, like so:

     template Flags(E) if (is(E == enum))
     {
         import std.format: format;
         import std.traits: EnumMembers;

         enum code = () {
             string result = "enum Flags {";
             foreach (e; EnumMembers!E)
                 result ~= format("%s = 1L << %dL,", e, e);
             return result ~ "}";
         }();
         mixin(code);
     }

     unittest
     {
         enum E
         {
             e0, e1, e2, e3, e4, e5, e6, e7, e8, e9,
             e10, e11, e12, e13, e14, e15, e16, e17, e18, e19,
             e20, e21, e22, e23, e24, e25, e26, e27, e28, e29,
             e30, e31, e32, e33, e34, e35, e36, e37, e38, e39,
             e40, e41, e42, e43, e44, e45, e46, e47, e48, e49,
             e50, e51, e52, e53, e54, e55, e56, e57, e58, e59,
             e60, e61, e62, e63
         }

         with (Flags!E)
         {
             assert((e0 | e1) == 0b11);
             assert((e1 | e3) == 0b1010);
             assert((e0 | e63) == 0x80_00_00_00_00_00_00_01);
             assert((e0 | e9 | e18 | e27 | e36 | e45 | e54 | e63) ==
                 0x80_40_20_10_08_04_02_01);

             assert(0b1010 & e1);
             assert(0b1010 & e3);
             assert(!(0b1010 & e0));
             assert(!(0b1010 & e2));
             assert(!(0b1010 & e4));
         }
     }


More information about the Digitalmars-d-learn mailing list