Member function pointers

Adam D. Ruppe destructionator at gmail.com
Fri Jun 7 17:24:22 PDT 2013


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.


More information about the Digitalmars-d mailing list