pointers, functions, and uniform call syntax

Jonathan M Davis jmdavisProg at gmx.com
Mon Sep 3 11:45:42 PDT 2012


On Monday, September 03, 2012 14:13:10 monarch_dodra wrote:
> I was playing around with a very big struct, and told myself I
> wanted it allocated on the heap. This meant I was now
> manipulating S* instead of an S.
> 
> I thought "this should have zero impact, because in D, because
> "." is supposed to deference for you if needed."
> 
> I find it strange though that when trying to call a function with
> a pointer, the pointer isn't automatically dereferenced if needed:
> 
> ----
> struct S
> {
>    void foo();
> }
> void bar(S);
> void main()
> {
>    auto r = new S;
>    r.foo();
>    bar(r);  //derp
>    r.bar(); //derp
> };
> ----
> I find it strange, because I thought the entire point was to
> abstract way something was allocated to the way it was used: EG.
>  From a caller perspective, I don't care if r is on the stack or
> on the heap: I just want to call the method bar on the object in
> question.
> 
> Why does one consider a "free standing" function more ambiguous
> than a member function?
> 
> Things get even stranger if you mix in uniform call syntax.
> "r.foo()" works, but "r.bar()" doesn't?
> 
> Am I really forced into:
> ----
> struct S
> {
>    void foo();
> }
> void bar(S);
> void main()
> {
>    auto r = new S;
>    r.foo();
>    bar(*r);    //Groan
>    (*r).bar(); //Super Groan.
> };
> ----
> I feel as if I'm just back at square 1...

All that UFCS does is make it so that if the first parameter of a function is a 
given type, you can call that function on that type as if it were a member 
function. It's purely syntactic convenience.

void bar(S) {}

takes an S, not as S*, so I wouldn't expect UFCS to work with it and an S*.

. dereferences a pointer when accessing a member function or variable, because 
it works quite nicely to have it work that way and generally negates the need 
for a second operator (->). It's certainly _not_ true that the automatic 
dereferencing with . allows you to forget that something is a pointer. An 
operation which _could_ be on the pointer (e.g ==) will operate on the 
pointer, forcing you to dereference it. It's just that adding -> on top of . 
is unnecessary and complicates the language.

The choice to have . automatically dereference pointers when access members 
predates the idea of UFCS considerably, and I don't think that it was ever 
really considered how they two would interact.  The automatic dereferencing of 
a pointer doesn't really have anything to do with UFCS except for similarites 
of syntax. It's simply that if a variable is a pointer, and it points to a 
type which has a member with the same name as what's on the right-hand side of 
the dot, then that member function gets called. And technically, it doesn't 
even dereference the pointer, because the member function takes a pointer (as 
the invisible this pointer). If there is no such member function, then free 
functions are checked to see if they take the variable's type as their first 
argument. If there's such a function with the right name and number of 
arguments, then it's used. For there to be any dereferencing involved would 
require special casing pointers, which doesn't currently happen.

I think that the way that it currently works is completely consistent. It's a 
perfectly valid enhancement request to want

void func(S s, int i) {...}

to be be callable with S*, given that normally function calls on an S* don't 
require you to dereference anything. But it's not like what we have is broken. 
It's just that there's a corner case which forces you to deal with pointers 
specially (which isn't exactly new, because things like == and assignment 
already require you to treat pointer specially). So, feel free to create an 
enhancement request. You've found a corner case that I suspect was never fully 
though through, and Walter may very well think that the change is worth 
making.

However, one thing to remember that complicates this a bit is that it's 
perfectly possible to declare a function which is overloaded with one function 
taking a pointer and one not.

void func(S* s, int i) {...}
void func(S s, int i) {...}

in which case, there's an ambiguity, and I would then expect UFCS to _not_ 
compile when using S*, or you'd risk function call hijacking. That's not 
necessarily a big deal, but it _does_ complicate things a bit.

- Jonathan M Davis


More information about the Digitalmars-d mailing list