On Friday, 7 June 2013 at 23:54:55 UTC, Manu wrote: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:
The properties are already there...
but they're not properly typed.
// 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.