Implicit enum conversions are a stupid PITA
Lutger
lutger.blijdestijn at gmail.com
Mon Mar 29 11:50:50 PDT 2010
Andrei Alexandrescu wrote:
> On 03/28/2010 03:44 AM, Lutger wrote:
(...)
>> I like this idea of implementing a flag type and tried to work something
>> out. Instead of implementing the overloads, it is also possible to
>> generate an enum via CTFE inside a struct and forward with alias this,
>> what do you think? I have tried this syntax, seems to work ok:
>>
>> alias Flags!q{ do_nothing,
>> walk_dog,
>> cook_breakfast,
>> deliver_newspaper,
>> visit_miss_kerbopple,
>> wash_covers } Todo;
>>
>> It does allow this though, but perhaps that can fixed:
>>
>> Todo todo = Todo.walk_dog;
>> todo |= 4;
>>
>> With such a type, it is easy to add some small convenience features, such
>> as
>> an enumToString, define property .max and implement a range that iterates
>> over all flags set or possible values.
>
> I think it will take some time to settle the competition between
> "template parses everything" approach and "language parses most" approach.
>
> For what it's worth, Walter and I were talking three years ago about a
> "new alias" feature:
>
> template Flags(new alias Names...) { ... }
>
> "new alias" means that you may pass to Flags symbols that haven't been
> defined. With that feature in place, Flags could be used like this:
>
> alias Flags!(do_nothing,
> walk_dog,
> cook_breakfast,
> deliver_newspaper,
> visit_miss_kerbopple,
> wash_covers)
> Todo;
>
> No muss, no fuss, no need to stringize anything. Flags could get to the
> names of the alias parameters by using Names[i].stringof.
>
> Well that's not in the language yet :o).
>
>
> Andrei
That would be nice, perhaps it's possible for a backwards-compatible 2.x
release to work something out. As bearophile said Ruby has some nice things
for metaprogramming that help with this, borrowed from lisp of course. I do
think Ruby's strong metaprogramming helped RoR become popular.
In the meantime I tried to hack Flags together and got stuck on this thing:
auto myTodoList = Todo.do_nothing;
Now myTodoList is of type uint (or whatever), not so nice - it's inconsistent
with regular named enums. It messed up making a range for iterating over the
flags set, which I thought was a nice addition.
Anyway, here is my attempt so far, please forgive me it is not so elegant:
import std.conv;
string genFlags(string names, string type = "uint")
{
string result = "enum : " ~ type ~ " { ";
int bits = 1;
size_t pos;
// skip leading whitespace
while( ++pos < names.length && names[pos] == ' ') { }
size_t prev = pos;
bool hasInitializer = false;
while ( pos < names.length)
{
if (names[pos] == '=')
hasInitializer = true;
else if (names[pos] == ',')
{
result ~= names[prev..pos];
if (!hasInitializer)
{
result ~= " = " ~ std.conv.to!string(bits);
bits <<= 1;
}
else
hasInitializer = false;
result ~= ',';
//skip whitespace
while( ++pos < names.length && names[pos] == ' ') { }
prev = pos;
}
pos++;
}
// concat the last flag
if (hasInitializer)
return result ~ names[prev..pos] ~ "}";
return result ~ names[prev..pos] ~ " = " ~ std.conv.to!string(bits) ~
"}";
}
struct Flags(string members, T = uint)
{
static assert( is(T : ulong), "Wrong underlying type of Flags: must be
integral not " ~ T.stringof );
mixin( genFlags(members) );
alias typeof(this) FlagsType;
T value;
alias value this;
this(T value)
{
this.value = value;
}
void opAssign(T value)
{
this.value = value;
}
pure bool opBinaryRight(string op, E)(E lhs) const
if ( op == "in" )
{
return cast(bool)(lhs & this);
}
}
unittest
{
alias Flags!q{ do_nothing, walk_dog, cook_breakfast, deliver_newspaper,
visit_miss_kerbopple, morning_task = walk_dog | cook_breakfast,
wash_covers } Todo;
Todo list1 = Todo.do_nothing;
assert( list1 == 1 );
list1 |= Todo.wash_covers | Todo.walk_dog;
assert(list1 == Todo.do_nothing | Todo.wash_covers | Todo.walk_dog);
assert(Todo.do_nothing in list1);
Todo list2 = Todo.cook_breakfast | Todo.wash_covers;
assert( list1 & list2 == Todo.do_nothing | Todo.cook_breakfast);
list1 = list2;
assert(list1 == Todo.do_nothing | Todo.wash_covers | Todo.walk_dog);
assert( Todo.morning_task == Todo.walk_dog | Todo.cook_breakfast );
auto list3 = Todo.deliver_newspaper;
assert(Todo.deliver_newspaper in list3, "can't infer type properly"); /*
bug */
}
More information about the Digitalmars-d
mailing list