pointers, functions, and uniform call syntax

Artur Skawina art.08.09 at gmail.com
Wed Sep 5 04:01:56 PDT 2012


On 09/04/12 20:03, Jonathan M Davis wrote:
> On Tuesday, September 04, 2012 12:51:53 Artur Skawina wrote:
>> On 09/03/12 20:45, Jonathan M Davis wrote:
>>> 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.
>>
>> No, it's not. See http://d.puremagic.com/issues/show_bug.cgi?id=8490 for why
>> this would be a very bad idea.
> 
> I completely disagree with that assessment. You already get copies with UFCS 
> all over the place.

The fact that something is already broken is not an argument for making things
even worse. As often in D, UFCS was done w/o properly thinking things through;
it should only work with free functions that take a ref argument.
Yes, accepting a value-passed arg is not technically "broken", but it /is/
unsound. 

   struct S { int i; }
   ref other_func_expecting_an_S_ref(ref S s) { return s; }
   int* whatever;

   auto f1(S s) { s.i = 42; }
   auto f2(S s) { whatever = &s.i; }
   auto f3(S s) { return other_func_expecting_an_S_ref(s); }

are just a few types of bugs that will appear other and other again. If an
UFCS "method" wants to work on a copy, it can create one (or just call another
non-ref-arg-non-UFCS function). Allowing the above means those bugs are not even
warned about by the compiler and much programmer time will be wasted looking for
the one place where someone forgot to add a 'ref' keyword while writing or
modifying the code (w/o realizing that 's' is a private copy and that the code
have been working previously only by luck).
And yes - enforcing the only-ref-arg rule would probably restrict UFCS a bit, in
that things that can be chained right now might no longer be usable that way, but
it's just a matter of fixing them, possibly by allowing annotations that
explicitly enable the call-by-value semantics for the type and/or function.
The defaults should however be safe.

Not introducing unsound features is easier than later removing them.


> It's just that if you could use a function which took the 
> struct as a value with a pointer to the struct with UFCS, then you'd get a 
> copy whereas if it's taking it by pointer, you wouldn't.

   S* s;
   auto f(S* sp) {/*...*/};
   s.f();

behaves as expected from an external POV, so there is no problem. Mistakenly
causing a copy is not likely, so implementation bugs are not an issue either.

   S* s;
   auto f(S s) {/*...*/};
   s.f();

*would* cause problems both for the 'f()' implementer /and/ user.


>> However 'void func(ref S s, ...){}' should
>> (be made to) work, at least for the UFCS case - if it already doesn't (old
>> compiler here...) 
> 
> I completely disagree with this as well. If using UFCS with an S* and a 
> function which takes an S works, then it should work with a function which 
> takes ref S, but pointers are _not_ the same as ref at all, and I completely 
> disagree with anything which try and make pointers convert to ref in the 
> general case. It only makes sense in this particular case, because of how 
> calling member functions on pointers to struct works, and in that case, the 
> ref is irrelevant IMHO. Having functions take ref is _annoying_, because you 
> can't pass rvalues to them, and so ref should be used sparingly.

Umm, UFCS *is* about calling "member functions" - this is exactly why UFCS
"methods" should be treated just like "normal" ones.
Allowing pass-this-by-value is an /extension/, which brings more harm than good.


> I don't see any real difference between having a function which takes an S 
> being used with UFCS and having that same function used with an S*. A copy 
> occurs in both cases. It's just that with S*, it means that the compiler has 

People working with pointer-to-structs are not going to expect that

   S* s;
   // ...
   s.method();

makes a copy of '*s' and operates on that. It's not how methods normally work;
it's way to easy for this to happen by mistake.


> to implicitly dereference it for you (as it already does when accessing the 
> struct's members). Other than that, the semantics are identical.
> 
>> - if 'func' is supposed to emulate a method then it
>> should behave like one.
> 
> Which is exactly the point of this enhancement request. If you call a member 
> function on a struct pointer, you don't need to dereference anything or really 
> care that it's a pointer, but with UFCS, all of a sudden you do, which breaks 
> the abstraction that UFCS is trying to provide.

If you call a member function on a struct pointer with UFCS all of a sudden you
need to care if it's a free function or a real method, because in the first case
the struct might be copied implicitly, and you get no help from the compiler to
detect such issues. That is (would be) the problem.


On 09/04/12 20:19, Era Scarecrow wrote:
>  I ask you, how do you check if it's a null pointer? &s?

Yes, obviously. If you need to do that manually.
 
>   int getx(ref S s)
>   //How does this make sense?? it looks wrong and is misleading
>   in {assert(&s); }
>   body {return s.x); }

It looks correct and is perfectly obvious. But see below - you don't
need to do this manually - the compiler does it for you when calling
methods and could handle the UFCS case too.

>  More importantly, if it's now a possibility do we have to start adding checks everywhere?
> 
>   int getx(S *s)
>   in {assert(s); } //perfectly acceptable check, we know it's a pointer
>   body {return s.x); }


   struct S { int i; auto f() { return i; } }
   int main() {
      S* s;
      return s.f();
   }

This program will assert at runtime (and (correctly) segfault in a release-build).
The compiler inserts not-null-this checks for "normal" methods in non-release
mode, and could also do that before invoking any UFCS "method". So you wouldn't
need to check for '!!&this' yourself.


The problem w/ these checks is that they can not be disabled per-type - which
prevents certain valid uses. The compiler-inserted assertions fire also when the
methods can deal with null-this themselves (happens eg. when dealing with 'C' APIs
and libraries, when you want to keep the C part as unmodified as possible).
But that is a different issue.

artur


More information about the Digitalmars-d mailing list