DIP66 1.2 (Multiple) alias this. Continuation of work.

Steven Schveighoffer via Digitalmars-d digitalmars-d at puremagic.com
Thu Apr 2 05:54:37 PDT 2015


On 3/30/15 3:19 PM, IgorStepanov wrote:
> On Monday, 30 March 2015 at 15:04:20 UTC, Steven Schveighoffer wrote:
>> On 3/29/15 1:34 PM, IgorStepanov wrote:
>>
>>> 1. We should reject types which use opDispatch and alias this at the
>>> same time.
>>
>> Why? Alias this has no filter. opDispatch can use template
>> constraints. It makes perfect sense to prefer opDispatch, unless it
>> doesn't have a valid match, and then use alias this instead.
>>
>> For example, if I wanted to wrap a type so I can instrument calls to
>> 'foo', I could do something like this:
>>
>> struct FooWrapper(T)
>> {
>>    T t;
>>    alias t this;
>>    auto opDispatch(string s, A...)(A args) if(s == "foo") {
>> writeln("calling foo"); return t.foo(args); }
>> }
>>
>> Why is this a bad use case?
>>
>> -Steve
>
> You can split this code to two structs:
>
> struct FooWrapper(T)
> {
>     struct FooDispatcher
>     {
>         auto opDispatch(string s, A...)(A args) {
>             writeln("calling ", s);
>         }
>     }
>     FooDispatcher d;
>     T t;
>     alias t this;
>     auto foo(string s, A...)(A args)
>     {
>        writeln("calling foo"); return t.foo(args);
>     }
> }
>
> FooWrapper!X x;
> x.foo(1, 2); //FooWrapper.foo has been called
> x.bar(1, 2); //FooWrapper.d.opDispatch has been called
> X orig = x; //FooWrepper.t is returned.
>
> ===============
> Yes, this code is much more tricky, but it work as you wish.
> opDispatch + alias this may deliver many problems, if one of those has
> more high priority then the other.
> We want to implement alias this maximally strictly, and after that, try
> to find rules which may be safely relaxed.

Not exactly. Yes, you have successfully pointed out that using 
opDispatch to define one named method is sort of useless, but that was 
not my point. Imagine if you wanted to do that to all methods that were 
in a list. I can imagine a use case that logs all calls to a type that 
are defined by a list:

struct LogWrapper(T, string[] funcnames) { ... }

It is impossible to do this generically and also use alias this. In 
today's situation, I can't override *selected* pieces of alias this, I 
must override all of them, because opDispatch takes precedence, even if 
there is no overriding. For example, this doesn't work:

struct Wrapper(T)
{
    T t;
    alias t this;
    auto opDispatch(string name, A...)(A args) if(name == "foo")
    {
       mixin("return t." ~ name ~ "(args);");
    }
}

Call w.bar(), and even if T has a bar method, it fails to compile, 
because opDispatch *fully* eclipses alias this. Even fields cannot be 
accessed. However, alias this does still provide subtyping, I can call 
func(T t) with a Wrapper!T. This working aspect of alias this + 
opDispatch will break after your changes (though I'm not sure if this is 
a huge problem).

The reason to have opDispatch override alias this is because of the 
level of control in what to override. alias this does not allow fine 
grained overriding of certain pieces, it's all or nothing. In other 
words, with alias this having precedence, opDispatch becomes neutered. 
With opDispatch having precedence, one can select the pieces to allow to 
trickle through to alias this.

To me, because opDispatch is implemented in the most derived type, and 
because you can limit its effect, it should have total precedence over 
everything except other defined members in that derived type. It's a 
question of who is in charge of this derived type's API. opDispatch is a 
way to avoid boilerplating a ton of stuff. But the result of opDispatch 
should be considered first-class members.

And let us not kid ourselves. If we define an ordering for precedence 
here, it is not going to be changed in the future. Broken code will 
preclude that possibility. "Loosening the screws" does not mean "change 
the complete ordering of the feature."

-Steve


More information about the Digitalmars-d mailing list