Should we add `a * b` for vectors?

Timon Gehr timon.gehr at gmx.ch
Thu Oct 5 11:26:56 UTC 2017


On 05.10.2017 11:52, Walter Bright wrote:
> On 10/5/2017 2:13 AM, Timon Gehr wrote:
>> It's easy to explain why: In C++, operators are the _only_ functions 
>> that have UFCS.
>>
>> This is in stark contrast to D, where all functions _but_ operators 
>> have UFCS.
>>
>> The proposal was allow UFCS also for overloaded operators.
>>
>> Hence, this discussion is about UFCS. These are not really operator 
>> overloading issues.
> 
> Ok, but I'm not sure what the proposal was.
> 
> 
>> UFCS allows hijacking. For an example, see:
>> https://github.com/tgehr/d-compiler/pull/1#discussion-diff-89697186L85
> 
> That may be a bug in the compiler. Can you produce a small test case?

struct S{
     // string foo(){ return "hijacked!"; } // uncomment to hijack
}

string foo(S s){ return "not hijacked!"; }

void main(){
     S s;
     import std.stdio;
     writeln(s.foo());
}


> I know that some of the UFCS code was written without regard to hijacking.
> ...

I think it is by design. Lookup first tries to find members of the type, 
and only if they don't exist applies UFCS lookup. Therefore, if members 
are added to the type or made visible, this may hijack existing UFCS usages.

The way to fix it is to do UFCS lookup always and then error out if both 
UFCS and member lookup match, but there is probably quite some code 
relying on the fact that you can provide a custom implementation of a 
general UFCS function by just adding a member. Also, with the hijacking 
error if you actually meant the member function the only way out I see 
is to use __traits(getMember, ...) for disambiguation.

(BTW: Local imports allow hijacking too. Maybe this one could be fixed?)

> 
>> The intention of the code was to demonstrate that a type can pass 
>> isInputRange in the same module in which it does not support front. 
>> This is an example of surprising name lookup behavior.
> 
> I submit it is surprising only if you're used to ADL :-)
> ...

I'm not used to ADL. I think it is surprising because input ranges 
support front. ;) (It's not surprising to _me_, I'm rather familiar with 
D's features, especially those added before last year or so. My 
statement is more that it could be surprising to many, and that it is 
not necessarily reasonable to blame them for being surprised.)

> ...
> 
> D's name lookup rules were quite simple, and deliberately set up that 
> way. Unfortunately, most people thought they were unintuitive. It turns 
> out that simple algorithms are not intuitive, and we now have a fairly 
> complex lookup system. Martin and I implemented it, and probably neither 
> of us completely understands it. I find it regrettable that things have 
> gotten to that state.
> ...

It might make sense to sit down at some point and see what goals the 
complex rules try to achieve and then come up with simpler rules that 
achieve the same (or better) goals.

> 
>> Of course there is also the opposite problem. You can have a type that 
>> supports all range primitives via UFCS but does not pass isInputRange, 
>> because Phobos does not import the module where the primitives are 
>> defined. (This particular case is sometimes solved by ADL, sometimes 
>> not.)
>>  ...
> 
> I suggest for this case writing a wrapper type for Iota, with 
> front/empty/popFront as members of that wrapper, then it should be good 
> to go. It's hardly any more work than writing the free functions.

One thing that currently works is having the following string constant 
in a util.d file:

enum ufcs_=q{
     private{
         import std.typecons: Proxy;
         struct UFCS(T){ T payload; mixin Proxy!payload; }
         auto ufcs(T)(T arg)@trusted{ return *cast(UFCS!T*)&arg; }
     }
};

Then, the following code compiles and runs:

import util: ufcs_;
mixin(ufcs_);

struct Iota{ private int a,b; }
auto iota(int a,int b){ return Iota(a,b); }

@property int front(Iota i){ return i.a; }
@property bool empty(Iota i){ return i.a>=i.b; }
void popFront(ref Iota i){ ++i.a; }

void main(){
     import std.algorithm, std.stdio;
     iota(0,10).ufcs.each!writeln; // ok
}

This exploits what seems to be a serious encapsulation-breaking bug in 
std.typecons.Proxy in order to pick up all UFCS functions visible from 
the current module, but a safe version of this could be made.

> Doing 
> this kind of wrapper doesn't work for operator overloading, which brings 
> us back to ADL is for operator overloading.

Why does it not work for operator overloading?


More information about the Digitalmars-d mailing list