www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - advanced function binding

reply Moritz Warning <mwarning web.de> writes:
Hi,

I try to write a RPC interface in D and have problems
to start. I've already written the code in C++, but it became
quite unmanageable and ugly because I
had to write various workarounds.

The core problem is to hide an arbitrary (member) function pointer
in a class. The argument values are provided _later_ in
the form of a char[][] to a class function:
void setArgs(char[][] args)

The wrapped function is called later by a class function that should rely on
two template parameters only; the type of object the function will be called
on, and the return type:
R call(R,T)(T obj) { /**/ }
For the type conversion I have several global T convert(T)(char[]) functions.

I've read about std.bind and recursive templates but wasn't able to
make much use of them.
I have thought about to pass a lazy template delegate (with convert(T)(char[])
inside)
to std.bind, but that's apparently not possible

Maybe somebody can give me a push into the right direction. :-)
.
Mar 27 2007
next sibling parent reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Moritz Warning wrote:
 Hi,
 
 I try to write a RPC interface in D and have problems
 to start. I've already written the code in C++, but it became
 quite unmanageable and ugly because I
 had to write various workarounds.
 
 The core problem is to hide an arbitrary (member) function pointer
 in a class. The argument values are provided _later_ in
 the form of a char[][] to a class function:
 void setArgs(char[][] args)
 
 The wrapped function is called later by a class function that should rely on
two template parameters only; the type of object the function will be called
on, and the return type:
 R call(R,T)(T obj) { /**/ }
 For the type conversion I have several global T convert(T)(char[]) functions.
 
 I've read about std.bind and recursive templates but wasn't able to
 make much use of them.
 I have thought about to pass a lazy template delegate (with convert(T)(char[])
inside)
 to std.bind, but that's apparently not possible
 
 Maybe somebody can give me a push into the right direction. :-)
 .

This problem is highly analogous to how Pyd exposes D functions to Python. (Only instead of doing a char[] -> T conversion, Pyd does a PyObject* -> T conversion.) There are two issues involved here: First, the class is storing a pointer to a member function, a concept which D does not directly support. Instead, D has the generally more useful concept of delegates, which is a fat pointer combining the pointer to the member function and an object reference into a single package. While this is usually more useful, it can get in the way of writing a dispatch mechanism like you seem to want (and which Pyd uses to implement class wrapping). Luckily, you can access the internals of a delegate directly to get around this: class Foo { void foo() {} } { void delegate() dg; dg.funcptr = &Foo.foo; dg.ptr = new Foo; dg(); } Second is actually calling the function with the provided arguments. To explain how Pyd does it, I shall dust off this ancient pastebin, from when tuples were first added to the language: http://paste.dprogramming.com/dpbaqugv.php That paste is a rough outline of how Pyd's function wrapping works, and I think it is exactly what you are looking for. -- Kirk McDonald http://kirkmcdonald.blogspot.com Pyd: Connecting D and Python http://pyd.dsource.org
Mar 28 2007
parent reply Moritz Warning <moritzwarning web.de> writes:
 http://paste.dprogramming.com/dpbaqugv.php

Hi, thank you for your response! It was very helpful indeed. Here is the example code I'm experimenting on: import std.traits; import std.conv; import std.bind; void main() { Wrapper!(A, Foo.getBar) x = new Wrapper!(A, Foo.getBar)(Foo.getBar); } class Foo { void getBar() {} } class Bar { } class Wrapper(B, alias Func) { alias ReturnType!(Func) R; alias typeof(Func) * FuncPtr; alias typeof(ReturnType!(std.bind.bindAlias!(Func))) BindPtr; //type is DerefFunc!(R) or void? FuncPtr funcptr; BindPtr bindptr; //1. store pointer this(FuncPtr funcptr) { this.funcptr = funcptr; } //2. bind parameters void bind(char[][] u) { ParameterTypeTuple!(Func) t; foreach (i, arg; t) { t[i] = convert!(typeof(arg))(u[i]); } bindptr = std.bind.bindAlias!(Func)(t); //fn(t); } //3. call function on object R call(B b) { R delegate() dg; dg.funcptr = this.funcptr; //need to assign bindptr! dg.ptr = cast(void*) b; return dg(); } } T convert(T)(char[] u) { static if (is(T == int)) { return std.conv.toInt(u); } else static if (is(T == float)) { return std.conv.toFloat(u);; } else static if (is(T == char[])) { return u; } else static assert(false, "Unsupported type: " ~ T.stringof); } It doesn't compile because I cannot assign a bindAlias!(Func)(t) to bindptr. The compiler tells me the return type of bindAlias is void, when I look at the source I would guess it's DerefFunc!(R). Anyway, somehow I must be able to store the binded function and assign it to dg.funcptr in call(). What is going wrong?
Mar 28 2007
parent reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Moritz Warning wrote:
http://paste.dprogramming.com/dpbaqugv.php

Hi, thank you for your response! It was very helpful indeed. Here is the example code I'm experimenting on: import std.traits; import std.conv; import std.bind; void main() { Wrapper!(A, Foo.getBar) x = new Wrapper!(A, Foo.getBar)(Foo.getBar); } class Foo { void getBar() {} } class Bar { } class Wrapper(B, alias Func) { alias ReturnType!(Func) R; alias typeof(Func) * FuncPtr; alias typeof(ReturnType!(std.bind.bindAlias!(Func))) BindPtr; //type is DerefFunc!(R) or void? FuncPtr funcptr; BindPtr bindptr; //1. store pointer this(FuncPtr funcptr) { this.funcptr = funcptr; } //2. bind parameters void bind(char[][] u) { ParameterTypeTuple!(Func) t; foreach (i, arg; t) { t[i] = convert!(typeof(arg))(u[i]); } bindptr = std.bind.bindAlias!(Func)(t); //fn(t); } //3. call function on object R call(B b) { R delegate() dg; dg.funcptr = this.funcptr; //need to assign bindptr! dg.ptr = cast(void*) b; return dg(); } } T convert(T)(char[] u) { static if (is(T == int)) { return std.conv.toInt(u); } else static if (is(T == float)) { return std.conv.toFloat(u);; } else static if (is(T == char[])) { return u; } else static assert(false, "Unsupported type: " ~ T.stringof); } It doesn't compile because I cannot assign a bindAlias!(Func)(t) to bindptr. The compiler tells me the return type of bindAlias is void, when I look at the source I would guess it's DerefFunc!(R). Anyway, somehow I must be able to store the binded function and assign it to dg.funcptr in call(). What is going wrong?

D distinguishes between function types and function pointer types. That is, given this: void foo() {} The following are different types: typeof(foo) typeof(&foo) The former is a function type, and is not actually that useful. (You can't declare variables of this type, or use it as a function parameter or return type.) The latter is a function pointer, and is very useful. In that example, it is equivalent to the type "void function()". If you want to get a pointer to a function in D, you MUST use the address-of operator (as in &foo). Thus, the line: alias typeof(Func) * FuncPtr; should actually be: alias typeof(&Func) FuncPtr; You are passing the function to the class as both an alias template argument and a constructor argument. It is then available both as a template argument, and as a member variable. This seems redundant. Supplying it as a template argument is probably preferable. Furthermore, experience has shown me it is wise to allow the user to explicitly specify the type of the function. Thus, FuncPtr should actually be a third template parameter, like this: class Wrapper(B, alias Func, FuncPtr = typeof(&Func)) {} You can then dispense with the constructor and the funcptr member variable entirely. Next, do you really need to store the /converted/ arguments? Why not just store the char[][], and convert the arguments when the function is actually called? The class looks like this if you do that: class Wrapper(B, alias Func, FuncPtr = typeof(&Func) { alias ReturnType!(FuncPtr) R; alias ParameterTypeTuple!(FuncPtr) Params; char[][] args; void bind(char[][] u) { args = u; } R call(B b) { R delegate(Params) dg; dg.funcptr = &Func; dg.ptr = cast(void*)b; Params t; foreach (i, arg; t) { t[i] = convert!(typeof(arg))(this.args[i]); } return dg(t); } } Note that std.bind is not needed at all if it is done this way. -- Kirk McDonald http://kirkmcdonald.blogspot.com Pyd: Connecting D and Python http://pyd.dsource.org
Mar 28 2007
parent reply Moritz Warning <moritzwarning web.de> writes:
Hi,

thank you again!
I wasn't aware that the delegate can accept a ParameterTypeTuple
and the difference between the function type declarations.
Something I have learned, nice. :)

Anyway, one probably small problem remains; when I try to compile your code I
get:

Main.d:94: Error: this for getBar needs to be type Foo not type
Main.Wrapper!(Foo,getBar).Wrapper
Main.d:94: Error: cannot implicitly convert expression (&this.getBar) of type
void delegate(()) to void(*)()
Main.d:65: template instance Main.Wrapper!(Foo,getBar) error instantiating

line 94 is "dg.funcptr = &Func;"
line 65 is "Wrapper!(Foo, Foo.getBar) x = new Wrapper!(Foo, Foo.getBar)();"

It seems to me the compiler assumes the base class of &Func is not Foo, but the
surrounding object?! - weird.
I got rid of the second error adding cast(void(*)()), but it also leaves a bad
feeling. :P

Kirk McDonald Wrote:

 D distinguishes between function types and function pointer types. That 
 is, given this:
 
 void foo() {}
 
 The following are different types:
 
 typeof(foo)
 typeof(&foo)
 
 The former is a function type, and is not actually that useful. (You 
 can't declare variables of this type, or use it as a function parameter 
 or return type.) The latter is a function pointer, and is very useful. 
 In that example, it is equivalent to the type "void function()".
 
 If you want to get a pointer to a function in D, you MUST use the 
 address-of operator (as in &foo). Thus, the line:
      alias typeof(Func) * FuncPtr;
 should actually be:
      alias typeof(&Func) FuncPtr;
 
 You are passing the function to the class as both an alias template 
 argument and a constructor argument. It is then available both as a 
 template argument, and as a member variable. This seems redundant. 
 Supplying it as a template argument is probably preferable. Furthermore, 
 experience has shown me it is wise to allow the user to explicitly 
 specify the type of the function. Thus, FuncPtr should actually be a 
 third template parameter, like this:

 
 class Wrapper(B, alias Func, FuncPtr = typeof(&Func)) {}
 
 You can then dispense with the constructor and the funcptr member 
 variable entirely.
 
 Next, do you really need to store the /converted/ arguments? Why not 
 just store the char[][], and convert the arguments when the function is 
 actually called? The class looks like this if you do that:
 
 class Wrapper(B, alias Func, FuncPtr = typeof(&Func) {
      alias ReturnType!(FuncPtr) R;
      alias ParameterTypeTuple!(FuncPtr) Params;
      char[][] args;
      void bind(char[][] u) {
          args = u;
      }
      R call(B b) {
          R delegate(Params) dg;
          dg.funcptr = &Func;
          dg.ptr = cast(void*)b;
          Params t;
          foreach (i, arg; t) {
              t[i] = convert!(typeof(arg))(this.args[i]);
          }
          return dg(t);
      }
 }
 
 Note that std.bind is not needed at all if it is done this way.
 
 -- 
 Kirk McDonald
 http://kirkmcdonald.blogspot.com
 Pyd: Connecting D and Python
 http://pyd.dsource.org

Mar 28 2007
parent reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Moritz Warning wrote:
 Hi,
 
 thank you again!
 I wasn't aware that the delegate can accept a ParameterTypeTuple
 and the difference between the function type declarations.
 Something I have learned, nice. :)
 
 Anyway, one probably small problem remains; when I try to compile your code I
get:
 
 Main.d:94: Error: this for getBar needs to be type Foo not type
Main.Wrapper!(Foo,getBar).Wrapper
 Main.d:94: Error: cannot implicitly convert expression (&this.getBar) of type
void delegate(()) to void(*)()
 Main.d:65: template instance Main.Wrapper!(Foo,getBar) error instantiating
 
 line 94 is "dg.funcptr = &Func;"
 line 65 is "Wrapper!(Foo, Foo.getBar) x = new Wrapper!(Foo, Foo.getBar)();"
 
 It seems to me the compiler assumes the base class of &Func is not Foo, but
the surrounding object?! - weird.
 I got rid of the second error adding cast(void(*)()), but it also leaves a bad
feeling. :P
 

Oh! I've seen this before. When you take the address of a member function, any member function, even one of an unrelated class, from inside a class, the compiler wants to take it as a delegate with 'this' as the context. It then complains that 'this' is of the wrong type, as you see in the first error. For a better feeling, try cast(FuncPtr) instead. One other thing: You can re-write that assignment using D's type inference. You can also drop the trailing parentheses. auto x = new Wrapper!(Foo, Foo.getBar); Isn't that much better? :-) -- Kirk McDonald http://kirkmcdonald.blogspot.com Pyd: Connecting D and Python http://pyd.dsource.org
Mar 29 2007
parent reply Moritz Warning <moritzwarning web.de> writes:
dg.funcptr = cast(FuncPtr) &Func;
The compiler resists the cast and gives an error again.

So I get this error:
Main.d:104: Error: this for getBar needs to be type Foo not type
Main.Wrapper!(Foo,getBar).Wrapper
Main.d:65: template instance Main.Wrapper!(Foo,getBar) error instantiating


Kirk McDonald Wrote:
 
 Oh! I've seen this before. When you take the address of a member 
 function, any member function, even one of an unrelated class, from 
 inside a class, the compiler wants to take it as a delegate with 'this' 
 as the context. It then complains that 'this' is of the wrong type, as 
 you see in the first error. For a better feeling, try cast(FuncPtr) instead.
 
 One other thing: You can re-write that assignment using D's type 
 inference. You can also drop the trailing parentheses.
 
 auto x = new Wrapper!(Foo, Foo.getBar);
 
 Isn't that much better? :-)

 
 -- 
 Kirk McDonald
 http://kirkmcdonald.blogspot.com
 Pyd: Connecting D and Python
 http://pyd.dsource.org

Mar 29 2007
parent reply Moritz Warning <moritzwarning web.de> writes:
After some additional input by Kirk (thanks!), this come up for solution:

class Wrapper(B, alias Func, FuncPtr = typeof(&Func)
{
	alias ReturnType!(FuncPtr) R;
	alias ParameterTypeTuple!(FuncPtr) Params;
	char[][] args;
	
	void bind(char[][] u) {
		args = u;
	}
	
	private static FuncPtr get_funcptr() { return &Func; }

	R call(B b) {
		R delegate(Params) dg;
		dg.funcptr = get_funcptr();
		dg.ptr = cast(void*)b;
		Params t;
		foreach (i, arg; t) {
			t[i] = convert!(typeof(arg))(this.args[i]);
		}
		return dg(t);
	}
}

Thread.join()  // ;-)
Mar 30 2007
parent Jason House <jason.james.house gmail.com> writes:
Out of curiosity, not have call accept a delegate instead of a class? 
That would make wrapper independent of the specific class (B)...

Moritz Warning wrote:
 After some additional input by Kirk (thanks!), this come up for solution:
 
 class Wrapper(B, alias Func, FuncPtr = typeof(&Func)
 {
 	alias ReturnType!(FuncPtr) R;
 	alias ParameterTypeTuple!(FuncPtr) Params;
 	char[][] args;
 	
 	void bind(char[][] u) {
 		args = u;
 	}
 	
 	private static FuncPtr get_funcptr() { return &Func; }
 
 	R call(B b) {
 		R delegate(Params) dg;
 		dg.funcptr = get_funcptr();
 		dg.ptr = cast(void*)b;
 		Params t;
 		foreach (i, arg; t) {
 			t[i] = convert!(typeof(arg))(this.args[i]);
 		}
 		return dg(t);
 	}
 }
 
 Thread.join()  // ;-)

Apr 16 2007
prev sibling parent Jason House <jason.james.house gmail.com> writes:
Wow, this sounds exactly like a set of C++ code that I plan to convert 
to D in the near future.  I look forward to seeing your final resolution 
to this problem.

Moritz Warning wrote:
 Hi,
 
 I try to write a RPC interface in D and have problems
 to start. I've already written the code in C++, but it became
 quite unmanageable and ugly because I
 had to write various workarounds.
 
 The core problem is to hide an arbitrary (member) function pointer
 in a class. The argument values are provided _later_ in
 the form of a char[][] to a class function:
 void setArgs(char[][] args)
 
 The wrapped function is called later by a class function that should rely on
two template parameters only; the type of object the function will be called
on, and the return type:
 R call(R,T)(T obj) { /**/ }
 For the type conversion I have several global T convert(T)(char[]) functions.
 
 I've read about std.bind and recursive templates but wasn't able to
 make much use of them.
 I have thought about to pass a lazy template delegate (with convert(T)(char[])
inside)
 to std.bind, but that's apparently not possible
 
 Maybe somebody can give me a push into the right direction. :-)
 .

Mar 29 2007