Uniform Function Call Syntax(UFCS) and @property

Steven Schveighoffer schveiguy at yahoo.com
Mon Mar 7 11:34:58 PST 2011


On Mon, 07 Mar 2011 13:28:26 -0500, kenji hara <k.hara.pg at gmail.com> wrote:

>> The problem is when there is a conflict between an actual member  
>> funtion,
>> and a free function which takes the type as its first argument.
>>
>> Who wins?  The obvious choice is the member function.  But let's say the
>> member function is added to the type long after the external function
>> exists.  People who have written code that uses the external function  
>> via
>> UFCS all of a sudden are now calling the member function, without  
>> warning.
>>  This is a form of hijacking.
>
> Why do you prefer module functions? It will be selected by UFCS only
> when there is no member that has the same name in class/struct. It is
> clear.

I'll give you an example.  This assumes that the compiler silently prefers  
member functions over free ones.

Say you are using a struct from a library you didn't write like this:

struct Foo
{
    int x;
}

And you want to read data into it.

So you write your own function:

void read(ref Foo f) { readf("%d", &f.x); }

So now, you can write stuff like:

Foo f;
f.read();

and things work great!

But a few years later, the author of foo decides to add serialization to  
Foo, and does:

struct Foo
{
    int x;
    void read() { readf("%x", &f.x); }
    void write() {  writef("%x", f.x); }
}

All of a sudden, all the code you had that did f.read() now calls the  
member, which does something subtly different.

>
>> You could say an ambiguous case should be an error, but then someone is  
>> able
>> to issue a sort of "denial of service" attack by simply defining a free
>> function which coincides with a member function.
>
> I don't understand the meaning of "denial of service" what you say. Is
> it causing compile error? It is same behavior with overload set
> conflict.

If the ambiguity is simply flagged as an error by the compiler, by simply  
defining a function with the same name outside the class or struct, you  
can deny access to the member function.

Following the above example, if my localized read(ref Foo f) is defined in  
a common library module, anyone who uses my code (but not my read  
function) now wants to use the new Foo.read member function, it always  
results in a compile error.  In essence, there is actually no way to  
access that member unless you stop importing my module.  Maybe I could  
care less that it's a problem you have, I've since changed all my code to  
read(f) instead of f.read() to workaround my problem.

These problems allow either hijacking of a function, or denying an object  
it's basic ability to define new members that are specific to itself.  For  
all this confusion and pain, I think it's not worth the benefit of being  
able to do f.read() vs. read(f).

>
>> With builtins, there is no conflict.  You cannot define member  
>> functions for
>> integers or for arrays, so the ambiguity is gone.
>
> Almost yes. (Builtin array has 'length' property. It may  conflict
> module function 'length'.)

Yes, but those are builtin properties, disallowed from day 1.   
Essentially, there is no way for an ambiguity to 'creep' into the code,  
since you could never call your length function via arr.length().

>> I see almost no benefit for having UFCS on classes and structs.  It is
>> already possible to add member functions to those types, and the  
>> difference
>> between x(y) and y.x() is extremely trivial.  If anything, this makes  
>> member
>> functions lose their specificity -- a member function is no longer
>> attributed to the struct or class, it can be defined anywhere.
>
> UFCS is veri usuful. It reduce fat interfaces of class/struct, and
> separate 'useful but surrounded operation' from core operations of the
> object.
>
> A simple example is std.stdio.File.writefln. This function does the
> almost same job std.stdio.writefln. Correct UFCS will cut wastes like
> this.

std.stdio.writefln forwards *directly* to stdout.writefln.  Boilerplate  
code like this should be inlined out.  I see zero waste here.

Actually, the way writefln works, I think you picked an example that would  
not be affected by UFCS.  writefln without a File argument would still  
need to exist in order to select stdout as the destination.

But in any case, the difference between calling a free function and a  
member is so small that there really is no gain.  You want to define a  
function that works on multiple types?  It works, you just have to call  
func(t, ...) instead of t.func(...).  I'd rather keep member functions as  
members, and external functions as external functions, as it adds to the  
understanding/readability.

And the potential for abuse grows.

For example, this should probably work today (not tested):

"%d".writefln(5);

And with UFCS, I now have a slew of types which allow this kind of abuse.

-Steve


More information about the Digitalmars-d mailing list