Jump to page: 1 2
Thread overview
A template for method forwarding?
Dec 12, 2008
Bill Baxter
Dec 12, 2008
dsimcha
Dec 12, 2008
Bill Baxter
Dec 12, 2008
dsimcha
Dec 12, 2008
Bill Baxter
Dec 13, 2008
Christopher Wright
Dec 13, 2008
Denis Koroskin
Dec 13, 2008
Christopher Wright
Dec 13, 2008
Christian Kamm
Dec 13, 2008
Christopher Wright
Dec 13, 2008
Fawzi Mohamed
Dec 12, 2008
Simen Kjaeraas
Dec 12, 2008
Bill Baxter
December 12, 2008
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
December 12, 2008
== 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();
}
December 12, 2008
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
December 12, 2008
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
December 12, 2008
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();
> }
>
December 12, 2008
== 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.
December 12, 2008
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
December 13, 2008
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.
December 13, 2008
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?
December 13, 2008
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.
« First   ‹ Prev
1 2