DIP66 - Multiple alias this

IgorStepanov via Digitalmars-d digitalmars-d at puremagic.com
Sun Oct 12 04:45:55 PDT 2014


On Sunday, 12 October 2014 at 08:36:05 UTC, Marc Schütz wrote:
> On Sunday, 12 October 2014 at 04:31:22 UTC, Walter Bright wrote:
>> On 10/11/2014 7:23 AM, IgorStepanov wrote:
>>> class A
>>> {
>>>   int i;
>>>   alias i this;
>>> }
>>>
>>> class B
>>> {
>>>   int i;
>>>   alias i this;
>>> }
>>>
>>> class C
>>> {
>>>   A a;
>>>   B b;
>>>   alias a this;
>>>   alias b this;
>>> }
>>>
>>> void foo(T)(T arg) if(is(T : int))
>>> {
>>>   ...
>>> }
>>>
>>> foo(C()); //Should it pass or not?
>>
>> There's a rule with imports that if the same symbol is 
>> reachable via multiple paths through the imports, that it is 
>> not an ambiguity error. Here, the same type is reachable 
>> through multiple alias this paths, so by analogy it shouldn't 
>> be an error.
>
> It's the same type, but different symbols; actual accesses 
> would be ambiguous. `is(T : int)` shouldn't evaluate to true if 
> `int a = T.init;` would fail.

I found an example of a situation that is bothering me.
Let we have a persistence framework, which provides a storing D 
object in some persistence storage: DB, file et c.

In introduces paired functions store/load and special type 
PersistenceObject.

If stored type is subtype of PersistenceObject it converts to 
PersistenceObject and PersistenceObject.load(stream) called for 
loading object (and PersistenceObject.store(stream) for storing).
Otherwice if object can't be converted to PersistenceObject it 
should be serialized via "serialize" function (or deserialized 
via "deserialize").

struct PersistenceFramework
{
    void store(T)(T arg) if (is(T : PersistenceObject))
    {
        PersistenceObject po = arg;
        arg.store(stream);
    }

    void store(T)(T arg) if (!is(T : PersistenceObject))
    {
        PersistenceObject po = arg;
        store(serialize(arg));
    }

    void load(T)(ref T arg) if (is(T : PersistenceObject))
    {
        PersistenceObject po = arg;
        arg.load(stream);
    }

    void load(T)(ref T arg) if (!is(T : PersistenceObject))
    {
        PersistenceObject po = arg;
        load(serialize(arg));
    }

    Stream stream;
}

/****************************************************************
And we have the next types which we want to store and load
*****************************************************************/

struct Role
{
     ...
}

struct User
{
    Role role;
    PersistenceObject po;
    //...
    alias role this;
    alias po this;
}

/*****************************************************************/

User u;
persistenceFramework.load(u)
//...
persistenceFramework.store(u);


/******************************************************************/
Role is not subtype of PersistenceObject thus all works ok.
We can store User via User.po and load it again;

Some time later, Role designer decided that Role should be 
subtype of PersistenceObject and changed Role definition:

struct Role
{
     ...
     PersistenceObject po;
     alias po this;
}

Now, User can not be converted to PersistenceObject because there 
are two path to convert: User.po and User.role.po;
Storing code after this change will be copiled successfully (if 
we follow your "is" rule), however object will be tried to load 
via "void load(T)(ref T arg) if (!is(T : PersistenceObject))".
Because object was saved via "void store(T)(T arg) if (is(T : 
PersistenceObject))" at the previous program run, user will not 
be loaded succesfully. Moreover, you will get an strange 
unexpected program behaviour and will be hard to find real error 
cause.

/*****************************************************************/
And finally, if you want to check, if you Type _really_ can be 
converted to AnotherType, you can use the next check:

void foo(Type)(Type arg) if (is(typeof({AnotherType x = 
Type.init;})))
{

}



More information about the Digitalmars-d mailing list