Thread overview | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
June 07, 2013 Member function pointers | ||||
---|---|---|---|---|
| ||||
Attachments:
| So from my dconf talk, I detailed a nasty hack to handle member function pointers in D. My approach is not portable, so I'd like to see an expression formalised in D, so this sort of interaction with C++ is possible, and also it may be useful in D code directly. I'm thinking something like this... Keen to hear thoughts. My approach was this: void function(T _this, ...args...); Explicit 'this' pointer; only works with ABI's that pass 'this' as the first integer argument. What I suggest is: void function(T this, ...args...); Note, I use keyword 'this' as the first argument. This is the key that distinguishes the expression as a member-function pointer rather than a typical function pointer. Calls through this function pointer would know to use the method calling convention rather than the static function calling convention. For 'extern(C++) void function(T this)', that would be to use the C++ 'thiscall' convention. I think this makes good sense, because other than the choice of calling convention, it really is just a 'function' in every other way. Now taken this as a declaration syntax, I think calls would be made via UFCS. T x; void function(T this) mp; mp(x); // I guess this is fine x.mp(); // but UFCS really makes this concept nice! So the final detail, is how to capture one of these member function pointers from within D... I initially thought about a syntax, but this is so niche, I don't think it warrants a syntax. So my current best idea is to introduce 2 properties to delegates, so that the function pointer (of this type) can be accessed via the delegate syntax. delegate d = &x.f; void function(T this) mp = d.funcPtr; An interesting side effect, is that 'delegate' could actually be understood as a strongly-typed small struct, whereas currently, it's just a magic thing: Given: RT delegate(A x, B y) d = &c.m; It would look like: struct delegate(C) { C thisPointer; RT function(C this, A x, B, y) funcPointer; } Currently, to get the instance or function pointers from a delegate, you need to do something like: delegate d; void** pd = cast(void**)&d; T instancePointer = cast(T)pd[0]; void function(T this) functionPointer = cast(RT function(T this))pd[1]; Casting through a void array like that is pretty horrible. Adding 2 properties to delegate to get either of those things can makes sense once it is possible to express the type of the function pointer, which would now be possible with the syntax above. So, I quite like the transparency introduced when a delegate can be explicitly described in the language. But there is one loose detail... void f() { void g() {} void delegate() d = &g; // delegate 'this' is a closure } I don't know a syntax to describe the type of a closure, so when a delegate is typed with a closure instead of a struct/class, what is 'C' in the struct template above? Thoughts? Is there reason to outright ban this sort of expression? I think this actually clarifies some details of the language, and reduces a currently 'magic' thing into a well-defined, strongly-typed concept. - Manu |
June 07, 2013 Re: Member function pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | On Friday, 7 June 2013 at 23:22:03 UTC, Manu wrote: > Currently, to get the instance or function pointers from a delegate, you need to do something like: delegates have two members, ptr and funcptr: http://dlang.org/function.html As a fun fact, if you modify druntime's allocator to be malloc(), you can use free(delegate.ptr) to manually manage closures, though this takes a lot of care to know if it actually should be freed or not. Anyway, the ptr member there is always void*, however, so at least one cast is required to actually use it. Perhaps the language could be extended to make this strongly typed, but then you'd have to change the whole visible type as assigning say, a closure to a delegate variable would need to be a different type than a class member; I guess this is what you're talking about though. idk, I've kinda wanted pointer to members before but I also think D's delegates being so versatile and interchangeable is totally boss. > I don't know a syntax to describe the type of a closure, so when a delegate is typed with a closure instead of a struct/class, > what is 'C' in the struct template above? That's a tricky one, perhaps it could be a tuple of the captured variables' type. |
June 07, 2013 Re: Member function pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | On 2013-06-07 23:21:53 +0000, Manu <turkeyman@gmail.com> said: > Thoughts? Reminds me of something similar I implemented a while ago: http://michelf.ca/projects/d-objc/syntax/#selector-literals Not only I think member function pointers are doable, but I think they're solely missing. There have been situations where I'd have used them but instead had to hack my way using a delegate. That was to build a bridge for Objective-C (before I decided to hack the compiler). I'd guess you're doing something of the sort too? -- Michel Fortin michel.fortin@michelf.ca http://michelf.ca/ |
June 07, 2013 Re: Member function pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe Attachments:
| On 8 June 2013 09:42, Adam D. Ruppe <destructionator@gmail.com> wrote: > On Friday, 7 June 2013 at 23:22:03 UTC, Manu wrote: > >> Currently, to get the instance or function pointers from a delegate, you need to do something like: >> > > delegates have two members, ptr and funcptr: http://dlang.org/function.html > > As a fun fact, if you modify druntime's allocator to be malloc(), you can use free(delegate.ptr) to manually manage closures, though this takes a lot of care to know if it actually should be freed or not. > > Anyway, the ptr member there is always void*, however, so at least one cast is required to actually use it. Perhaps the language could be extended to make this strongly typed, but then you'd have to change the whole visible type as assigning say, a closure to a delegate variable would need to be a different type than a class member; I guess this is what you're talking about though. > Indeed, I apologise for my ignorance! The properties are already there... but they're not properly typed. idk, I've kinda wanted pointer to members before but I also think D's > delegates being so versatile and interchangeable is totally boss. I agree, a delegate is almost always what I want. But a delegate is really just a compound concept, and without a way to express it's fundamental parts, which in certain circumstances (like in my case) are useful on their own, then it feels like a bit of magic. I don't know a syntax to describe the type of a closure, so when a >> delegate is typed with a closure instead of a struct/class, what is 'C' in the struct template above? >> > > That's a tricky one, perhaps it could be a tuple of the captured variables' type. > Yeah, that was my initial feeling too... and I think it could be quite workable in that way. |
June 07, 2013 Re: Member function pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Michel Fortin Attachments:
| On 8 June 2013 09:48, Michel Fortin <michel.fortin@michelf.ca> wrote:
> On 2013-06-07 23:21:53 +0000, Manu <turkeyman@gmail.com> said:
>
> Thoughts?
>>
>
> Reminds me of something similar I implemented a while ago: http://michelf.ca/projects/d-**objc/syntax/#selector-literals<http://michelf.ca/projects/d-objc/syntax/#selector-literals>
>
> Not only I think member function pointers are doable, but I think they're solely missing. There have been situations where I'd have used them but instead had to hack my way using a delegate. That was to build a bridge for Objective-C (before I decided to hack the compiler). I'd guess you're doing something of the sort too?
Precisely. The concept is already embedded inside of delegate, but delegate
is framed like a piece of magic, rather than a well defined compound of
more primitive pieces.
Those primitive pieces would be useful in certain cases (like mine, and
yours).
I think the only missing detail is a way to express a 'thiscall' function pointer, which I believe my suggestion satisfies quite nicely.
|
June 08, 2013 Re: Member function pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | On Friday, 7 June 2013 at 23:54:55 UTC, Manu wrote: > The properties are already there... > but they're not properly typed. I just don't think they can be unless we change the visible type which isn't always what we want.... but, check this out: // this new type keeps track of the exact type of the pointer // and manages the delegate so we can cast with some sanity... struct PointerToMemberFunction(Class, Ret, T...) if(is(Class : Object)) { private Ret delegate(T) dg; @property Class object() { return cast(Class) dg.ptr; } @property void object(Class rhs) { dg.ptr = cast(void*) rhs; } Ret opCall(T t) { assert(dg.ptr !is null, "null this"); static if(is(Ret == void)) dg(t); else return dg(t); } } // this helps us construct the above template ptrToMember(alias blargh) { // I'm writing out the function template longhand // because I want to use blargh as a type and // dmd won't let me do it without an intermediate // dmd complains "type expected, not __traits" so we use // this to work around it template workaround(T) { alias workaround = T; } alias ObjectType = workaround!(__traits(parent, blargh)); auto ptrToMember(ObjectType a = null) { import std.traits; PointerToMemberFunction!( ObjectType, ReturnType!blargh, ParameterTypeTuple!blargh ) mem; mem.dg.funcptr = &blargh; mem.dg.ptr = cast(void*) a; return mem; } } actually i just realized maybe this should not be a function at all, so it is easy to use as a class member, without having to write an extra typeof(). That's probably more valuable than the initialiser which as you'll see below is optional anyway. Anyway, then you can use it like this: class A { void foo() {writeln("foo from ", name);} int bar(string s) {writeln("bar ",s," from ", name); return 10; } this(string n) { name = n; } string name; } class B : A { this(string n) { super(n); } override void foo() { writeln("derived foo from ", name); } } void main() { A a = new A("a"); B c = new B("c"); Object ob = a; auto ptr = ptrToMember!(B.foo)(c); // initialize the object here ptr(); ptr.object = a; ptr(); auto ptr2 = ptrToMember!(A.bar); // initialize to null.. ptr2.object = ptr.object; // can assign later ptr2("hey"); } And if you play around with the types there, you'll see the compile will fail if you do something uncool. Though the error messages sometimes suck, what the hell: "test2.d(58): Error: not a property ptr.object"... actually it is, but I was passing it an A when it required a B. That's a type mismatch, not a missing property. But whatever, at least it *did* fail to compile, so we have our type safety. And if I threw in an alias this on a getter property, we could assign these beasties to regular delegates too. Not half bad. But regular delegate assigning to this is prohibited because we can't be sure their context pointer is the right kind of class. This would be true if the compiler automatically did the ptrToMember!() too, so let's say we change &a.foo to return one of these strongly typed things instead of a void* delegate like it does now. auto ptr = &a.foo; // is ptr a void delegate() or a pointer to specifically a void() member of class A? That matters because if the former (current behavior), this compiles: ptr = { writeln("hello!"); }; but if the latter, that will not, it will complain that the this pointers are of mismatching type (or "not a property" lol). Of course, with alias this style behavior, you could explicitly write out void delegate() ptr = &a.foo; // ok, use looser type ptr = {...}; // perfectly fine So I guess we wouldn't be losing much, but since we both agree a delegate is almost always what we want, maybe the library solution of ptrToMember is better to keep auto in a Just Works state. I'm presuming you're already doing something similar for your C++ interop, if not, I'm pretty sure this same idea would work there too, at least to a 'good enough' state, even if not technically portable. > Yeah, that was my initial feeling too... and I think it could be quite workable in that way. Aye, and this would require the compiler's help. Can't hit 'good enough' on that with templates. |
June 08, 2013 Re: Member function pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe Attachments:
| On 8 June 2013 10:24, Adam D. Ruppe <destructionator@gmail.com> wrote:
> On Friday, 7 June 2013 at 23:54:55 UTC, Manu wrote:
>
>> The properties are already there...
>> but they're not properly typed.
>>
>
> I just don't think they can be unless we change the visible type which isn't always what we want.... but, check this out:
>
> // this new type keeps track of the exact type of the pointer
> // and manages the delegate so we can cast with some sanity...
> struct PointerToMemberFunction(Class, Ret, T...) if(is(Class : Object)) {
> private Ret delegate(T) dg;
> @property Class object() { return cast(Class) dg.ptr; }
> @property void object(Class rhs) { dg.ptr = cast(void*) rhs; }
> Ret opCall(T t) {
> assert(dg.ptr !is null, "null this");
> static if(is(Ret == void))
> dg(t);
> else
> return dg(t);
> }
> }
>
> // this helps us construct the above
> template ptrToMember(alias blargh) {
> // I'm writing out the function template longhand
> // because I want to use blargh as a type and
> // dmd won't let me do it without an intermediate
>
> // dmd complains "type expected, not __traits" so we use
> // this to work around it
> template workaround(T) { alias workaround = T; }
>
> alias ObjectType = workaround!(__traits(parent, blargh));
>
> auto ptrToMember(ObjectType a = null) {
> import std.traits;
> PointerToMemberFunction!(
> ObjectType,
> ReturnType!blargh,
> ParameterTypeTuple!blargh
> ) mem;
> mem.dg.funcptr = &blargh;
> mem.dg.ptr = cast(void*) a;
> return mem;
> }
> }
>
>
> actually i just realized maybe this should not be a function at all, so it is easy to use as a class member, without having to write an extra typeof(). That's probably more valuable than the initialiser which as you'll see below is optional anyway.
>
> Anyway, then you can use it like this:
>
> class A {
> void foo() {writeln("foo from ", name);}
> int bar(string s) {writeln("bar ",s," from ", name); return 10; }
> this(string n) { name = n; }
> string name;
> }
>
> class B : A {
> this(string n) { super(n); }
> override void foo() { writeln("derived foo from ", name); }
> }
>
>
> void main() {
> A a = new A("a");
> B c = new B("c");
> Object ob = a;
>
> auto ptr = ptrToMember!(B.foo)(c); // initialize the object here
> ptr();
> ptr.object = a;
> ptr();
>
> auto ptr2 = ptrToMember!(A.bar); // initialize to null..
> ptr2.object = ptr.object; // can assign later
> ptr2("hey");
> }
>
>
> And if you play around with the types there, you'll see the compile will fail if you do something uncool. Though the error messages sometimes suck, what the hell: "test2.d(58): Error: not a property ptr.object"... actually it is, but I was passing it an A when it required a B. That's a type mismatch, not a missing property.
>
> But whatever, at least it *did* fail to compile, so we have our type safety.
>
>
> And if I threw in an alias this on a getter property, we could assign these beasties to regular delegates too. Not half bad.
>
> But regular delegate assigning to this is prohibited because we can't be sure their context pointer is the right kind of class. This would be true if the compiler automatically did the ptrToMember!() too, so let's say we change &a.foo to return one of these strongly typed things instead of a void* delegate like it does now.
>
> auto ptr = &a.foo; // is ptr a void delegate() or a pointer to
> specifically a void() member of class A?
>
>
> That matters because if the former (current behavior), this compiles:
>
> ptr = { writeln("hello!"); };
>
>
> but if the latter, that will not, it will complain that the this pointers are of mismatching type (or "not a property" lol). Of course, with alias this style behavior, you could explicitly write out
>
> void delegate() ptr = &a.foo; // ok, use looser type
> ptr = {...}; // perfectly fine
>
>
>
> So I guess we wouldn't be losing much, but since we both agree a delegate is almost always what we want, maybe the library solution of ptrToMember is better to keep auto in a Just Works state.
>
>
>
> I'm presuming you're already doing something similar for your C++ interop, if not, I'm pretty sure this same idea would work there too, at least to a 'good enough' state, even if not technically portable.
I initially started with something like this. But look how much code it is! I actually deleted it all and went for my non-portable hack.
My implementation was different. You've basically wrapped up a delegate,
and made something that emulates a delegate (I'm not sure why?).
I don't want a delegate, I want a function pointer. So my solution was
similar, but wrapped up a function pointer and aliased a delegate to the
correct type, then created a local delegate populating with 'this' and the
function at the call-site...
But it's all crap! It's really just abusing a delegate to get at the
concept it embed's which doesn't have a proper expression.
|
June 08, 2013 Re: Member function pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | On Saturday, 8 June 2013 at 01:11:46 UTC, Manu wrote: > I initially started with something like this. But look how much code it is! Yeah... > You've basically wrapped up a delegate, and made something that emulates a delegate > (I'm not sure why?). I just wanted to add type safety to a delegate; to get rid of the visible cast(void*) and vice versa, to see what it would look like. |
June 08, 2013 Re: Member function pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | On 2013-06-07 23:57:40 +0000, Manu <turkeyman@gmail.com> said: > Precisely. The concept is already embedded inside of delegate, but delegate > is framed like a piece of magic, rather than a well defined compound of > more primitive pieces. Delegates are not parametrized on the type of "this", which makes them easier to move around. I would not change delegates. But function pointers with a "this" parameter would be useful. You can achieve this using a template struct containing a pointer and a call method: the call method would generate a local delegate variable from the pointer and and call it. What you can't do without compiler support is get such a pointer in a type-safe manner. -- Michel Fortin michel.fortin@michelf.ca http://michelf.ca/ |
June 08, 2013 Re: Member function pointers | ||||
---|---|---|---|---|
| ||||
Posted in reply to Michel Fortin Attachments:
| On 8 June 2013 12:29, Michel Fortin <michel.fortin@michelf.ca> wrote: > On 2013-06-07 23:57:40 +0000, Manu <turkeyman@gmail.com> said: > > Precisely. The concept is already embedded inside of delegate, but >> delegate >> is framed like a piece of magic, rather than a well defined compound of >> more primitive pieces. >> > > Delegates are not parametrized on the type of "this", which makes them easier to move around. I would not change delegates. > Actually, that's very true. Good point, and I think this is almost the key distinction. But function pointers with a "this" parameter would be useful. You can > achieve this using a template struct containing a pointer and a call method: the call method would generate a local delegate variable from the pointer and and call it. What you can't do without compiler support is get such a pointer in a type-safe manner. Yup, this is what I originally did. It's big and ugly, I didn't like it, and deleted it. I think the syntax I suggest is simple and obvious, and naturally, typesafe. |
Copyright © 1999-2021 by the D Language Foundation