www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Pass function (not alias) to template and/or delegate-ize a template

reply cc <cc nevernet.com> writes:
Is it possible to pass a class member function as a template 
argument in such a way that it can 1. be called during runtime, 
and 2. still retrieve the UDAs of the function itself?  For 
example, given this setup:

struct MYUDA {}
class Foo {
	 (MYUDA) int bar(int x) { return x*2; }
}
auto foo = new Foo();

Neither of these will work:

void FCALL(alias F, V...)(V args) {
	assert(hasUDA!(F, MYUDA)); // Ok
	//assert(F(args) == 10); // Compilation Error: need `this` for 
`bar` of type `int(int x)`
}
FCALL!(foo.bar)(5);

void DCALL(V...)(int delegate(V) func, V args) {
	//assert(hasUDA!(func, MYUDA)); // Assertion failure (runtime 
delegate can't see compile-time UDAs)
	assert(func(args) == 12); // Ok
}
DCALL(&foo.bar, 6);


However, I can combine them into one function like so:

void TCALL(alias F, V...)(int delegate(V) func, V args) {
	assert(hasUDA!(F, MYUDA)); // Ok
	assert(func(args) == 14); // Ok
}
TCALL!(foo.bar)(&foo.bar, 7);

But it feels ugly having to write the same thing twice (foo.bar 
and &foo.bar).  I'd like to simply this somehow.  Given a 
template declaration like "void CALL(alias F)( ... )" is there 
some way to write a function parameter that takes a 
delegate-of-F?  Such that I can ultimate call the function as 
CALL(&foo.bar) and the template parameter alias is inferred.

I did figure out I can do it with mixins like so:

void MCALL(string func, V...)(V args) {
	mixin(`assert(hasUDA!(`~func~`, MYUDA));`); // Ok
	mixin(`assert(`~func~`(args) == 16);`); // Ok
}
MCALL!"foo.bar"(8);

Which works.  Is this the "best" way to do this?  Or is there 
another pattern that looks cleaner?
Jun 10 2018
parent ag0aep6g <anonymous example.com> writes:
On 06/11/2018 08:46 AM, cc wrote:
 struct MYUDA {}
 class Foo {
       (MYUDA) int bar(int x) { return x*2; }
 }
 auto foo = new Foo();
[...]
 I did figure out I can do it with mixins like so:
 
 void MCALL(string func, V...)(V args) {
      mixin(`assert(hasUDA!(`~func~`, MYUDA));`); // Ok
      mixin(`assert(`~func~`(args) == 16);`); // Ok
 }
 MCALL!"foo.bar"(8);
 
 Which works.  Is this the "best" way to do this?
That only works when foo is visible to MCALL. That's a serious limitation.
 Or is there another pattern that looks cleaner?
I can't think of a really nice solution where you just pass `foo.bar` or `&foo.bar`. The problem is that an alias of `foo.bar` is really an alias of `Foo.bar`, i.e. there is no connection to the object `foo`. And `&foo.bar` is just a generic delegate. There is no (compile-time) type information that links it to the type `Foo`. The best I could come up with is passing the method's name separately from the object: ---- void main() { auto foo = new Foo(); foo.nCall!"bar"(9); } void nCall(string methodName, O, V...)(O obj, V args) /* 'n' is for "name" */ { import std.traits: hasUDA; auto methodDelegate = mixin("&obj." ~ methodName); mixin("alias methodAlias = obj." ~ methodName ~ ";"); assert(hasUDA!(methodAlias, MYUDA)); // Ok assert(methodDelegate(args) == 18); // Ok } struct MYUDA {} class Foo { (MYUDA) int bar(int x) { return x*2; } } ---- If it's feasible to hard-code a list of all possible object types into the "call" function, then you can pass `&foo.bar` and find the correct object type by trying them all: ---- void main() { auto foo = new Foo(); bCall(&foo.bar, 9); } void bCall(D, V...)(D methodDelegate, V args) /* 'b' is for "bloat" */ { import std.traits: hasUDA; import std.meta: AliasSeq; assert(methodDelegate(args) == 18); // Ok auto obj = cast(Object) methodDelegate.ptr; if (obj is null) return; /* Delegate is not of a method. */ static foreach (O; AliasSeq!(Foo, /* Bar, Baz, etc */)) { static foreach (mName; __traits(allMembers, O)) {{ alias m = AliasSeq!(__traits(getMember, O, mName))[0]; static if (is(typeof(&m))) { if (typeid(O) == typeid(obj) && &m is cast(void*) methodDelegate.funcptr) { assert(hasUDA!(m, MYUDA)); // Ok } } }} } } struct MYUDA {} class Foo { (MYUDA) int bar(int x) { return x*2; } } ---- But that's probably not feasible.
Jun 11 2018