On Mon, 07 Mar 2011 13:28:26 -0500, kenji hara <k.hara...@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

Reply via email to