www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Address of UFCS call implicity converts to Delegate

reply Jonathan Marler <johnnymarler gmail.com> writes:
This feels like a natural extension to existing semantics. It 
doesn't require new syntax and serves as a solution to some 
issues when working with delegates.

Say some API wants a delegate like this: void delegate(string arg)

With this feature, you could take a function like this:

void myCoolFunction(MyClassObject obj, string arg)
{
     // ...
}

and the following would have the same delegate type:

&myClassObject.myCoolFunction // type is void delegate(string arg)



This of course wouldn't work for all functions.  The first 
parameter of the function would need to have the same calling 
convention as the "this" parameter for a "delegate".

void cantBecomeDelegate(SomeBigStructType s)
{
}
&myStruct.cantBecomeDelegate // Error

error: cannot convert UFCS call to delegate because the first 
parameter of function 'cantBecomeDelegate' is too large.  
Consider adding "ref" to the first parameter or making it a 
pointer.

To fix this you could do something like this:
void canBecomeDelegate(ref SomeBigStructType s)
{
}
&myStruct.canBecomeDelegate // OK!
Apr 23
parent reply Basile B. <b2.temp gmx.com> writes:
On Sunday, 23 April 2017 at 16:32:06 UTC, Jonathan Marler wrote:
 This feels like a natural extension to existing semantics. It 
 doesn't require new syntax and serves as a solution to some 
 issues when working with delegates.

 Say some API wants a delegate like this: void delegate(string 
 arg)

 With this feature, you could take a function like this:

 void myCoolFunction(MyClassObject obj, string arg)
 {
     // ...
 }

 and the following would have the same delegate type:

 &myClassObject.myCoolFunction // type is void delegate(string 
 arg)



 This of course wouldn't work for all functions.  The first 
 parameter of the function would need to have the same calling 
 convention as the "this" parameter for a "delegate".

 void cantBecomeDelegate(SomeBigStructType s)
 {
 }
 &myStruct.cantBecomeDelegate // Error

 error: cannot convert UFCS call to delegate because the first 
 parameter of function 'cantBecomeDelegate' is too large.  
 Consider adding "ref" to the first parameter or making it a 
 pointer.

 To fix this you could do something like this:
 void canBecomeDelegate(ref SomeBigStructType s)
 {
 }
 &myStruct.canBecomeDelegate // OK!
What would be the usage of this ? Actually i think i see the ABI trick you want to use. And it works: ================== import std.stdio; alias ProtoD = void delegate(size_t); alias ProtoF = void function(size_t); class Foo{size_t a;} extern(C) void pseudoMemberFunc(Foo foo, size_t a) { foo.a = a;} void main() { Foo foo = new Foo; // under the hood it's what would make "&foo.pseudoMemberFunc;" ProtoD dg; dg.funcptr = cast(ProtoF) &pseudoMemberFunc; dg.ptr = cast(void*) foo; // close the hood carefully dg(42); writeln(foo.a); } It works in extern(C) only because of parameter order. ================== But: 1/ It's dangerous because then nothing guarantees anymore that .funcptr is a n actual member function. 2/ Why not just a member function ?
Apr 23
parent reply Jonathan Marler <johnnymarler gmail.com> writes:
On Sunday, 23 April 2017 at 17:00:59 UTC, Basile B. wrote:
 2/ Why not just a member function ?
For the same reason that UFCS exists. You can't add "member functions" to external library types.
Apr 23
parent reply Basile B. <b2.temp gmx.com> writes:
On Sunday, 23 April 2017 at 17:07:51 UTC, Jonathan Marler wrote:
 On Sunday, 23 April 2017 at 17:00:59 UTC, Basile B. wrote:
 2/ Why not just a member function ?
For the same reason that UFCS exists. You can't add "member functions" to external library types.
Good point. I have to say that this feature then makes me think to what's called "class helpers" in the Delphi/Object Pascal world. That's exactly for what they're used.
Apr 23
parent reply Jonathan Marler <johnnymarler gmail.com> writes:
On Sunday, 23 April 2017 at 17:13:31 UTC, Basile B. wrote:
 On Sunday, 23 April 2017 at 17:07:51 UTC, Jonathan Marler wrote:
 On Sunday, 23 April 2017 at 17:00:59 UTC, Basile B. wrote:
 2/ Why not just a member function ?
For the same reason that UFCS exists. You can't add "member functions" to external library types.
Good point. I have to say that this feature then makes me think to what's called "class helpers" in the Delphi/Object Pascal world. That's exactly for what they're used.
I've added a DIP for this (https://github.com/dlang/DIPs/pull/61). At first I first thought that all we needed was to add semantics to take the address of a UFCS-style call, but after messing around with your example I realized that delegates are not ABI-compatible with functions that take the delegate ptr as the first parameter. You mentioned that the problem was with the parameter order and that this should work with extern(C) functions and I think you're right. The new DIP proposes the addition of "Extension Methods" which are functions that are ABI-compatible with delegates. You define an extension method by naming the first parameter "this": struct Foo { void bar(int x) { } } void baz(ref Foo this, int x) { } Because the first parameter of baz is named this, it is an "extension method" of Foo which means it is ABI-compatible with the method bar. void delegate(int x) dg; Foo foo; dg = &foo.bar; // a normal method delegate dg(42); // calls foo.bar(42) dg = &foo.baz; // an extension method delegate dg(42); // calls baz(foo, 42); dg = &baz; // a "null delegate", unsafe code, funcptr points to the baz function, but ptr is null dg(42); // calls baz(null, 42);
Apr 24
next sibling parent Jonathan Marler <johnnymarler gmail.com> writes:
On Monday, 24 April 2017 at 15:47:14 UTC, Jonathan Marler wrote:
 On Sunday, 23 April 2017 at 17:13:31 UTC, Basile B. wrote:
 [...]
I've added a DIP for this (https://github.com/dlang/DIPs/pull/61). At first I first thought that all we needed was to add semantics to take the address of a UFCS-style call, but after messing around with your example I realized that delegates are not ABI-compatible with functions that take the delegate ptr as the first parameter. You mentioned that the problem was with the parameter order and that this should work with extern(C) functions and I think you're right. The new DIP proposes the addition of "Extension Methods" which are functions that are ABI-compatible with delegates. You define an extension method by naming the first parameter "this": [...]
Scratch the "Extension Methods" idea. I've found a more generalized way to do the same thing. I'm calling it "Delegateable Functions" (https://github.com/dlang/DIPs/pull/61).
Apr 24
prev sibling parent reply Meta <jared771 gmail.com> writes:
On Monday, 24 April 2017 at 15:47:14 UTC, Jonathan Marler wrote:
 I've added a DIP for this 
 (https://github.com/dlang/DIPs/pull/61).

 At first I first thought that all we needed was to add 
 semantics to take the address of a UFCS-style call, but after 
 messing around with your example I realized that delegates are 
 not ABI-compatible with functions that take the delegate ptr as 
 the first parameter.  You mentioned that the problem was with 
 the parameter order and that this should work with extern(C) 
 functions and I think you're right.

 The new DIP proposes the addition of "Extension Methods" which 
 are functions that are ABI-compatible with delegates. You 
 define an extension method by naming the first parameter "this":

 struct Foo
 {
     void bar(int x)
     {
     }
 }
 void baz(ref Foo this, int x)
 {
 }

 Because the first parameter of baz is named this, it is an 
 "extension method" of Foo which means it is ABI-compatible with 
 the method bar.

 void delegate(int x) dg;
 Foo foo;

 dg = &foo.bar;  // a normal method delegate
 dg(42);         // calls foo.bar(42)

 dg = &foo.baz;  // an extension method delegate
 dg(42);         // calls baz(foo, 42);

 dg = &baz;      // a "null delegate", unsafe code, funcptr 
 points to the baz function, but ptr is null
 dg(42);         // calls baz(null, 42);
One small tweak is that `this` should act as a storage class instead of the user having to name the parameter `this`. This is what C# does so we should mimic it to avoid confusion. https://www.codeproject.com/Tips/709310/Extension-Method-In-Csharp
Apr 24
parent Jonathan Marler <johnnymarler gmail.com> writes:
On Monday, 24 April 2017 at 19:19:27 UTC, Meta wrote:
 On Monday, 24 April 2017 at 15:47:14 UTC, Jonathan Marler wrote:
 I've added a DIP for this 
 (https://github.com/dlang/DIPs/pull/61).

 At first I first thought that all we needed was to add 
 semantics to take the address of a UFCS-style call, but after 
 messing around with your example I realized that delegates are 
 not ABI-compatible with functions that take the delegate ptr 
 as the first parameter.  You mentioned that the problem was 
 with the parameter order and that this should work with 
 extern(C) functions and I think you're right.

 The new DIP proposes the addition of "Extension Methods" which 
 are functions that are ABI-compatible with delegates. You 
 define an extension method by naming the first parameter 
 "this":

 struct Foo
 {
     void bar(int x)
     {
     }
 }
 void baz(ref Foo this, int x)
 {
 }

 Because the first parameter of baz is named this, it is an 
 "extension method" of Foo which means it is ABI-compatible 
 with the method bar.

 void delegate(int x) dg;
 Foo foo;

 dg = &foo.bar;  // a normal method delegate
 dg(42);         // calls foo.bar(42)

 dg = &foo.baz;  // an extension method delegate
 dg(42);         // calls baz(foo, 42);

 dg = &baz;      // a "null delegate", unsafe code, funcptr 
 points to the baz function, but ptr is null
 dg(42);         // calls baz(null, 42);
One small tweak is that `this` should act as a storage class instead of the user having to name the parameter `this`. This is what C# does so we should mimic it to avoid confusion. https://www.codeproject.com/Tips/709310/Extension-Method-In-Csharp
Yes I'm familiar with C# extension methods and that was my initial thought. The one advantage I saw with naming the parameter "this" was that it produces more "refactorable" code. If the first parameter of a delegateable function is a reference to a struct or a class, then you could move the function inside the struct/class, take out the first parameter and the code will work as a member function with no changes since the "this" keyword will be referring to the same object in both cases. But it's not a big deal, either syntax works fine in my opinion.
Apr 24