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