www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - A template for method forwarding?

reply "Bill Baxter" <wbaxter gmail.com> writes:
Let's say you want to use object composition instead of inheritance.
Now you want to forward half-a-dozen method from the new to class to
the composed class, like so:

class NewClass
{
     ImplT implementor;
     ...
     // Do some method forwarding
     void func1(int a, float b) { implementor.func1(a,b); }
     string func2(string s) { return implementor.func2(s); }
     T aTemplate(T)(T val, T[] arr) { return implementor!(T)(val,arr); }
     ...
}

It becomes pretty tedious to type all these things out, and if the
base class changes a method signature, you have to remember to do it
in the parent class too.
So the challenge is to write some kind of template that does the
necessary argument deduction to implement a forwarder just by
mentioning the name of the method and the object to forward to.
Something like this perhaps for the usage syntax:

    mixin call_forward!(implementor, "func1");
    mixin call_forward!(implementor, "func2");
    mixin call_forward!(implementor, "aTemplate");

Is it possible?  Somebody must have done something like this already.

--bb
Dec 12 2008
next sibling parent reply dsimcha <dsimcha yahoo.com> writes:
== Quote from Bill Baxter (wbaxter gmail.com)'s article
 Let's say you want to use object composition instead of inheritance.
 Now you want to forward half-a-dozen method from the new to class to
 the composed class, like so:
 class NewClass
 {
      ImplT implementor;
      ...
      // Do some method forwarding
      void func1(int a, float b) { implementor.func1(a,b); }
      string func2(string s) { return implementor.func2(s); }
      T aTemplate(T)(T val, T[] arr) { return implementor!(T)(val,arr); }
      ...
 }
 It becomes pretty tedious to type all these things out, and if the
 base class changes a method signature, you have to remember to do it
 in the parent class too.
 So the challenge is to write some kind of template that does the
 necessary argument deduction to implement a forwarder just by
 mentioning the name of the method and the object to forward to.
 Something like this perhaps for the usage syntax:
     mixin call_forward!(implementor, "func1");
     mixin call_forward!(implementor, "func2");
     mixin call_forward!(implementor, "aTemplate");
 Is it possible?  Somebody must have done something like this already.
 --bb
That was fun. Disclaimer: This probably is impossible in D1. This is probably strictly a D2 hack. The one bug I see is that this template will not handle default parameters correctly (or at all). import std.traits; template Forward(string clName, Methods...) { static if(Methods.length == 1) { mixin ForwardImpl!(clName, Methods[0]); } else { mixin ForwardImpl!(clName, Methods[0]); mixin Forward!(clName, Methods[1..$]); } } template ForwardImpl(string clName, string method) { private mixin("alias ParameterTypeTuple!(" ~ clName ~ "." ~ method ~ ") params;"); private mixin("alias ReturnType!(" ~ clName ~ "." ~ method ~ ") retType;"); static if(is(retType == void)) { mixin("void " ~ method ~ "(Tuple!" ~ params.stringof ~ " args){" ~ clName ~ "." ~ method ~ "(args); }"); } else { mixin(retType.stringof ~ " " ~ method ~ "(Tuple!" ~ params.stringof ~ " args){ return " ~ clName ~ "." ~ method ~ "(args); }"); } } template Tuple(T...) { alias T Tuple; } // Test code. class Mul { // The class you are delegating to. this() {} uint multiply(uint l, uint r) { return l * r; } uint divide(uint l, uint r) { return l / r; } void testVoid() { writeln("Success: testVoid"); } } class Arithmetic { Mul mul; this() { mul = new Mul; } uint add(uint l, uint r) { return l + r; } mixin Forward!("mul", "multiply", "divide", "testVoid"); } import std.stdio; void main() { auto arith = new Arithmetic; writeln(arith.multiply(2, 3)); writeln(arith.divide(4, 2)); arith.testVoid(); }
Dec 12 2008
next sibling parent reply "Bill Baxter" <wbaxter gmail.com> writes:
Cool.  I don't see anything D2 specific there, so I think it should
work in D1 ok.
std.traits.ParameterTypeTuple and std.traits.ReturnType both exist in
D1, if that's what you were worried about.

I think there may be a problem with 0-arg functions in the code?
You handle the void return type (which I'm not sure is necessary
actually -- I think D lets you say "return foo()" for a void function
specifically to handle this kind of template situation), but I think
maybe you don't handle a void argument?    I'm getting an error with
that for some reason... will dig more.

--bb

On Sat, Dec 13, 2008 at 5:54 AM, dsimcha <dsimcha yahoo.com> wrote:
 == Quote from Bill Baxter (wbaxter gmail.com)'s article
 Let's say you want to use object composition instead of inheritance.
 Now you want to forward half-a-dozen method from the new to class to
 the composed class, like so:
 class NewClass
 {
      ImplT implementor;
      ...
      // Do some method forwarding
      void func1(int a, float b) { implementor.func1(a,b); }
      string func2(string s) { return implementor.func2(s); }
      T aTemplate(T)(T val, T[] arr) { return implementor!(T)(val,arr); }
      ...
 }
 It becomes pretty tedious to type all these things out, and if the
 base class changes a method signature, you have to remember to do it
 in the parent class too.
 So the challenge is to write some kind of template that does the
 necessary argument deduction to implement a forwarder just by
 mentioning the name of the method and the object to forward to.
 Something like this perhaps for the usage syntax:
     mixin call_forward!(implementor, "func1");
     mixin call_forward!(implementor, "func2");
     mixin call_forward!(implementor, "aTemplate");
 Is it possible?  Somebody must have done something like this already.
 --bb
That was fun. Disclaimer: This probably is impossible in D1. This is probably strictly a D2 hack. The one bug I see is that this template will not handle default parameters correctly (or at all). import std.traits; template Forward(string clName, Methods...) { static if(Methods.length == 1) { mixin ForwardImpl!(clName, Methods[0]); } else { mixin ForwardImpl!(clName, Methods[0]); mixin Forward!(clName, Methods[1..$]); } } template ForwardImpl(string clName, string method) { private mixin("alias ParameterTypeTuple!(" ~ clName ~ "." ~ method ~ ") params;"); private mixin("alias ReturnType!(" ~ clName ~ "." ~ method ~ ") retType;"); static if(is(retType == void)) { mixin("void " ~ method ~ "(Tuple!" ~ params.stringof ~ " args){" ~ clName ~ "." ~ method ~ "(args); }"); } else { mixin(retType.stringof ~ " " ~ method ~ "(Tuple!" ~ params.stringof ~ " args){ return " ~ clName ~ "." ~ method ~ "(args); }"); } } template Tuple(T...) { alias T Tuple; } // Test code. class Mul { // The class you are delegating to. this() {} uint multiply(uint l, uint r) { return l * r; } uint divide(uint l, uint r) { return l / r; } void testVoid() { writeln("Success: testVoid"); } } class Arithmetic { Mul mul; this() { mul = new Mul; } uint add(uint l, uint r) { return l + r; } mixin Forward!("mul", "multiply", "divide", "testVoid"); } import std.stdio; void main() { auto arith = new Arithmetic; writeln(arith.multiply(2, 3)); writeln(arith.divide(4, 2)); arith.testVoid(); }
Dec 12 2008
parent dsimcha <dsimcha yahoo.com> writes:
== Quote from Bill Baxter (wbaxter gmail.com)'s article
 Cool.  I don't see anything D2 specific there, so I think it should
 work in D1 ok.
 std.traits.ParameterTypeTuple and std.traits.ReturnType both exist in
 D1, if that's what you were worried about.
 I think there may be a problem with 0-arg functions in the code?
 You handle the void return type (which I'm not sure is necessary
 actually -- I think D lets you say "return foo()" for a void function
 specifically to handle this kind of template situation), but I think
 maybe you don't handle a void argument?    I'm getting an error with
 that for some reason... will dig more.
 --bb
IDK, it works for me on DMD 2.21, including the no arguments case. Your mileage may vary on other compiler versions, since this is some really hackish metaprogramming.
Dec 12 2008
prev sibling parent reply "Bill Baxter" <wbaxter gmail.com> writes:
On Sat, Dec 13, 2008 at 6:13 AM, Bill Baxter <wbaxter gmail.com> wrote:
 Cool.  I don't see anything D2 specific there, so I think it should
 work in D1 ok.
 std.traits.ParameterTypeTuple and std.traits.ReturnType both exist in
 D1, if that's what you were worried about.

 I think there may be a problem with 0-arg functions in the code?
 You handle the void return type (which I'm not sure is necessary
 actually -- I think D lets you say "return foo()" for a void function
 specifically to handle this kind of template situation), but I think
 maybe you don't handle a void argument?    I'm getting an error with
 that for some reason... will dig more.
Actually I think it's not zero-arg functions. The problem is that one of my arguments is a templated type. The .stringof says the parameter type is TheTemplate, when the argument type is really TheTemplate!(T). So when your code tries to generate the function it tries to make it void aFunction(TheTemplate x) { ... }, which obviously ain't gonna fly. So looks like bad stringof is the culprit, once again. --bb
Dec 12 2008
next sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Bill Baxter wrote:
 On Sat, Dec 13, 2008 at 6:13 AM, Bill Baxter <wbaxter gmail.com> wrote:
 Cool.  I don't see anything D2 specific there, so I think it should
 work in D1 ok.
 std.traits.ParameterTypeTuple and std.traits.ReturnType both exist in
 D1, if that's what you were worried about.

 I think there may be a problem with 0-arg functions in the code?
 You handle the void return type (which I'm not sure is necessary
 actually -- I think D lets you say "return foo()" for a void function
 specifically to handle this kind of template situation), but I think
 maybe you don't handle a void argument?    I'm getting an error with
 that for some reason... will dig more.
Actually I think it's not zero-arg functions. The problem is that one of my arguments is a templated type. The .stringof says the parameter type is TheTemplate, when the argument type is really TheTemplate!(T). So when your code tries to generate the function it tries to make it void aFunction(TheTemplate x) { ... }, which obviously ain't gonna fly. So looks like bad stringof is the culprit, once again. --bb
This case is a known bug and has a patch. Really, stringof should give a fully qualified name, and any template parameters should use fully qualified names. The existing patch doesn't do that, but it's a minor improvement.
Dec 12 2008
parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Sat, 13 Dec 2008 03:27:42 +0300, Christopher Wright <dhasenan gmail.com>
wrote:

 Bill Baxter wrote:
 On Sat, Dec 13, 2008 at 6:13 AM, Bill Baxter <wbaxter gmail.com> wrote:
 Cool.  I don't see anything D2 specific there, so I think it should
 work in D1 ok.
 std.traits.ParameterTypeTuple and std.traits.ReturnType both exist in
 D1, if that's what you were worried about.

 I think there may be a problem with 0-arg functions in the code?
 You handle the void return type (which I'm not sure is necessary
 actually -- I think D lets you say "return foo()" for a void function
 specifically to handle this kind of template situation), but I think
 maybe you don't handle a void argument?    I'm getting an error with
 that for some reason... will dig more.
Actually I think it's not zero-arg functions. The problem is that one of my arguments is a templated type. The .stringof says the parameter type is TheTemplate, when the argument type is really TheTemplate!(T). So when your code tries to generate the function it tries to make it void aFunction(TheTemplate x) { ... }, which obviously ain't gonna fly. So looks like bad stringof is the culprit, once again. --bb
This case is a known bug and has a patch. Really, stringof should give a fully qualified name, and any template parameters should use fully qualified names. The existing patch doesn't do that, but it's a minor improvement.
I don't know if it is a good thing to have. Do you understand how much bigger executable size becomes?
Dec 12 2008
parent Christopher Wright <dhasenan gmail.com> writes:
Denis Koroskin wrote:
 I don't know if it is a good thing to have. Do you understand how much 
 bigger executable size becomes?
If the .stringof is only used in templates, then there is no increase -- except insofar as you are able to do more with D, which allows you to build more and possibly larger applications. If it's used in CTFE functions, that can be an issue. In the average case, you've got string constants increased in size by maybe a factor of four (ballpark guess). Probably not more than a factor of ten. I'm not sure what portion of a DMD binary, on average, is devoted to type name string constants.
Dec 12 2008
prev sibling parent reply Christian Kamm <kamm-incasoftware removethis.de> writes:
 So looks like bad stringof is the culprit, once again.
Why use stringof when you have an alias? This works for me in D1 - if you discount default arguments and parameter storage classes (will templates ever be able to touch these?). template Forward(string clName, Methods...) { static if(Methods.length == 1) { mixin ForwardImpl!(clName, Methods[0]); } else { mixin ForwardImpl!(clName, Methods[0]); mixin Forward!(clName, Methods[1..$]); } } template ForwardImpl(string clName, string method) { private mixin("alias ParameterTypeTuple!(" ~ clName ~ "." ~ method ~ ") params;"); private mixin("alias ReturnType!(" ~ clName ~ "." ~ method ~ ") retType;"); mixin("retType " ~ method ~ "(params args){ return " ~ clName ~ "." ~ method ~ "(args); }"); }
Dec 13 2008
parent reply Christopher Wright <dhasenan gmail.com> writes:
Christian Kamm wrote:
 So looks like bad stringof is the culprit, once again.
Why use stringof when you have an alias? This works for me in D1 - if you discount default arguments and parameter storage classes (will templates ever be able to touch these?).
stringof sucks, but it's easy to work with. Your solution doesn't deal with method overloads. Passing in a method alias would work: template Forward(string classname, alias method) { mixin ("ReturnTypeOf!(method) " ~ method.stringof ~ "(ParameterTupleOf!(Method) parameters) { return composed." ~ method.stringof ~ "(parameters); }"); } Except method.stringof doesn't work, so you have to do it like this: template strof(alias method) { const strof = (&method).stringof[2..$]; } template Forward(char[] classname, alias method) { mixin ("ReturnTypeOf!(method) " ~ strof!(method) ~ "(ParameterTupleOf!(method) parameters) { return composed." ~ strof!(method) ~ "(parameters); }"); }
Dec 13 2008
parent Fawzi Mohamed <fmohamed mac.com> writes:
On 2008-12-13 15:29:19 +0100, Christopher Wright <dhasenan gmail.com> said:

 Christian Kamm wrote:
 So looks like bad stringof is the culprit, once again.
Why use stringof when you have an alias? This works for me in D1 - if you discount default arguments and parameter storage classes (will templates ever be able to touch these?).
stringof sucks, but it's easy to work with. Your solution doesn't deal with method overloads. Passing in a method alias would work: template Forward(string classname, alias method) { mixin ("ReturnTypeOf!(method) " ~ method.stringof ~ "(ParameterTupleOf!(Method) parameters) { return composed." ~ method.stringof ~ "(parameters); }"); } Except method.stringof doesn't work, so you have to do it like this: template strof(alias method) { const strof = (&method).stringof[2..$]; } template Forward(char[] classname, alias method) { mixin ("ReturnTypeOf!(method) " ~ strof!(method) ~ "(ParameterTupleOf!(method) parameters) { return composed." ~ strof!(method) ~ "(parameters); }"); }
In my testing framework I had first a version using stringof, and I was able to pass the type or alias directly, this solved many issues. I think that if doable passing types and aliases is indeed better than strings. somewhat related to this topic I just found out that is(... return/function/delegate) behave in a way that I did not expect, given a method that returns an int and one that has an int argument (get/set) one has: typeof(&A.ret_int):int function() typeof(&A.ret_int) U==return:int typeof(&A.init.ret_int):int delegate() typeof(&A.init.ret_int) T==return:int typeof(&A.init.ret_int) T==delegate:int typeof(&A.ret_void):void function(int a) typeof(&A.ret_void) U==return:int typeof(&A.init.ret_void):void delegate(int a) typeof(&A.init.ret_void) T==return:int typeof(&A.init.ret_void) T==delegate:int for me it is ok as I wanted to have the type of the property getter/setter, but it is not what I would have expected from the documentation... The prviosu running this program {{{ import tango.io.Stdout; class A{ int ret_int(){ return 3; } void ret_void(int a){ } } void main(){ Stdout("typeof(&A.ret_int):")(typeof(&A.ret_int).stringof).newline; static if(is(typeof(&A.ret_int) U==function)) Stdout("typeof(&A.ret_int) U==function:")(U.stringof).newline; static if(is(typeof(&A.ret_int) U==return)) Stdout("typeof(&A.ret_int) U==return:")(U.stringof).newline; Stdout("typeof(&A.init.ret_int):")(typeof(&A.init.ret_int).stringof).newline; static if(is(typeof(&A.init.ret_int) T==return)){ Stdout("typeof(&A.init.ret_int) T==return:")(T.stringof).newline; } static if(is(typeof(&A.init.ret_int) T==function)){ Stdout("typeof(&A.init.ret_int) T==function:")(T.stringof).newline; } static if(is(typeof(&A.init.ret_int) T==delegate)){ Stdout("typeof(&A.init.ret_int) T==delegate:")(T.stringof).newline; static if(is(T U==function)) Stdout("&A.init.ret_int delegate function:")(U.stringof).newline; static if(is(T U==return)) Stdout("&A.init.ret_int delegate return:")(U.stringof).newline; } Stdout("typeof(&A.ret_void):")(typeof(&A.ret_void).stringof).newline; static if(is(typeof(&A.ret_void) U==function)) Stdout("typeof(&A.ret_void) U==function:")(U.stringof).newline; static if(is(typeof(&A.ret_void) U==return)) Stdout("typeof(&A.ret_void) U==return:")(U.stringof).newline; Stdout("typeof(&A.init.ret_void):")(typeof(&A.init.ret_void).stringof).newline; static if(is(typeof(&A.init.ret_void) T==return)){ Stdout("typeof(&A.init.ret_void) T==return:")(T.stringof).newline; } static if(is(typeof(&A.init.ret_void) T==delegate)){ Stdout("typeof(&A.init.ret_void) T==delegate:")(T.stringof).newline; static if(is(T U==function)) Stdout("typeof(&A.init.ret_void) delegate function:")(U.stringof).newline; static if(is(T U==return)) Stdout("typeof(&A.init.ret_void) delegate return:")(U.stringof).newline; } } }}}
Dec 13 2008
prev sibling parent reply "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Fri, 12 Dec 2008 21:08:51 +0100, Bill Baxter <wbaxter gmail.com> wrote:

 Let's say you want to use object composition instead of inheritance.
 Now you want to forward half-a-dozen method from the new to class to
 the composed class, like so:

 class NewClass
 {
      ImplT implementor;
      ...
      // Do some method forwarding
      void func1(int a, float b) { implementor.func1(a,b); }
      string func2(string s) { return implementor.func2(s); }
      T aTemplate(T)(T val, T[] arr) { return implementor!(T)(val,arr); }
      ...
 }

 It becomes pretty tedious to type all these things out, and if the
 base class changes a method signature, you have to remember to do it
 in the parent class too.
 So the challenge is to write some kind of template that does the
 necessary argument deduction to implement a forwarder just by
 mentioning the name of the method and the object to forward to.
 Something like this perhaps for the usage syntax:

     mixin call_forward!(implementor, "func1");
     mixin call_forward!(implementor, "func2");
     mixin call_forward!(implementor, "aTemplate");

 Is it possible?  Somebody must have done something like this already.

 --bb
Wouldn't opDot do what you want here? -- Simen
Dec 12 2008
parent "Bill Baxter" <wbaxter gmail.com> writes:
On Sat, Dec 13, 2008 at 6:00 AM, Simen Kjaeraas <simen.kjaras gmail.com> wrote:
 On Fri, 12 Dec 2008 21:08:51 +0100, Bill Baxter <wbaxter gmail.com> wrote:

 Let's say you want to use object composition instead of inheritance.
 Now you want to forward half-a-dozen method from the new to class to
 the composed class, like so:

 class NewClass
 {
     ImplT implementor;
     ...
     // Do some method forwarding
     void func1(int a, float b) { implementor.func1(a,b); }
     string func2(string s) { return implementor.func2(s); }
     T aTemplate(T)(T val, T[] arr) { return implementor!(T)(val,arr); }
     ...
 }

 It becomes pretty tedious to type all these things out, and if the
 base class changes a method signature, you have to remember to do it
 in the parent class too.
 So the challenge is to write some kind of template that does the
 necessary argument deduction to implement a forwarder just by
 mentioning the name of the method and the object to forward to.
 Something like this perhaps for the usage syntax:

    mixin call_forward!(implementor, "func1");
    mixin call_forward!(implementor, "func2");
    mixin call_forward!(implementor, "aTemplate");

 Is it possible?  Somebody must have done something like this already.

 --bb
Wouldn't opDot do what you want here?
Not really. I don't want to willy nilly forward all method to implementor. I may want to hide some methods, or I may be composing two different objects and want to forward some methods to one and some to the other. --bb
Dec 12 2008