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