www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - The most awesome "forward to member" solution?

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Hey folks,


So in working with the allocator, a common matter has come again to the 
fore: I need to forward certain functions to a member. Consider code in 
https://github.com/andralex/phobos/blob/allocator/std/experimental/allocator/free_tree.d:

struct FreeTree(ParentAllocator)
{
     ...

     static if (hasMember!(ParentAllocator, "expand"))
     bool expand(ref void[] b, size_t delta)
     {
         return parent.expand(b, delta);
     }

     static if (hasMember!(ParentAllocator, "reallocate"))
     bool reallocate(ref void[] b, size_t n)
     {
         return parent.reallocate(b, n);
     }

     static if (hasMember!(ParentAllocator, "allocateAll"))
     void[] allocateAll()
     {
         return parent.allocateAll;
     }
}

A simple solution have FreeTree support these functions would be to use 
simple subtyping with alias this. However, I'd like more control than 
that - yes, I want forwarding to work but not accessing the parent 
object directly.

So I'm thinking of defining a mixin that would be used like this:

struct FreeTree(ParentAllocator)
{
     ...
     mixin(forwardIfDefined("parent",
         "expand", "reallocate", "allocateAll"));
}

and, boom, all appropriate forwarding is generated, either with sheer 
definitions or by using opDispatch.

Furthermore, we should probably define this for Phobos because it's a 
common thing people like to do.

Before embarking on this, does anyone have some code around that does 
that kind of stuff?


Andrei
May 02 2015
next sibling parent reply "Dicebot" <public dicebot.lv> writes:
Sounds similar to http://dlang.org/phobos/std_typecons.html#.Proxy
May 02 2015
next sibling parent reply "Meta" <jared771 gmail.com> writes:
On Sunday, 3 May 2015 at 00:25:13 UTC, Dicebot wrote:
 Sounds similar to 
 http://dlang.org/phobos/std_typecons.html#.Proxy
That's a good idea. Proxy could be improved to take a list of names of members to forward. That'd be pretty cool actually.
May 02 2015
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/2/15 5:42 PM, Meta wrote:
 On Sunday, 3 May 2015 at 00:25:13 UTC, Dicebot wrote:
 Sounds similar to http://dlang.org/phobos/std_typecons.html#.Proxy
That's a good idea. Proxy could be improved to take a list of names of members to forward. That'd be pretty cool actually.
Here's what I have right now - simple as they come: http://dpaste.dzfl.pl/7ec11459a125. Kudos to whoever defined ParameterTypeTuple, it's exactly what the doctor prescribed. The gist of it is: string forwardToMember(string member, string[] funs...) { string result; foreach (fun; funs) { result ~= " auto "~fun~"(ParameterTypeTuple!(typeof("~member~"."~fun~")) args) { return "~member~"."~fun~"(args); } "; } return result; } which is used as: mixin(forwardToMember("member", "expand", "reallocate", "owns")); Is this a common thing people wanna do? Put in Phobos? Andrei
May 02 2015
next sibling parent reply "Meta" <jared771 gmail.com> writes:
On Sunday, 3 May 2015 at 04:20:46 UTC, Andrei Alexandrescu wrote:
 On 5/2/15 5:42 PM, Meta wrote:
 On Sunday, 3 May 2015 at 00:25:13 UTC, Dicebot wrote:
Here's what I have right now - simple as they come: http://dpaste.dzfl.pl/7ec11459a125. Kudos to whoever defined ParameterTypeTuple, it's exactly what the doctor prescribed. The gist of it is: string forwardToMember(string member, string[] funs...) { string result; foreach (fun; funs) { result ~= " auto "~fun~"(ParameterTypeTuple!(typeof("~member~"."~fun~")) args) { return "~member~"."~fun~"(args); } "; } return result; } which is used as: mixin(forwardToMember("member", "expand", "reallocate", "owns")); Is this a common thing people wanna do? Put in Phobos? Andrei
Is there any particular reason to generate 1 function per parent function? I was actually surprised the following doesn't work: struct Test1 { void foo(string) { writeln("Test1 foo"); } int bar(int) { writeln("Test1 bar"); return 0; } void baz() { writeln("Test1 baz"); } } struct TestWrapper(T) { T parent; alias foo = parent.foo; alias bar = parent.bar; alias baz = parent.baz; } void main() { TestWrapper!Test1 t; //Error: this for foo needs to be type Test1 not type TestWrapper!(Test1) t.foo("test"); //ditto t.bar(2); //ditto t.baz(); } It seems like it'd be a lot cheaper and cleaner to just be able to alias the parent method. Also, it could probably be made a bit simpler with opDispatch.
May 02 2015
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/2/15 10:00 PM, Meta wrote:
 It seems like it'd be a lot cheaper and cleaner to just be able to alias
 the parent method.
Yeh, that's the first solution that comes to mind. alias doesn't work here but of course we could change the language.
 Also, it could probably be made a bit simpler with
 opDispatch.
I'd have to see the code, but my intuition is that things could get quite a bit more hairy. Andrei
May 02 2015
parent reply "Meta" <jared771 gmail.com> writes:
On Sunday, 3 May 2015 at 05:49:52 UTC, Andrei Alexandrescu wrote:
 On 5/2/15 10:00 PM, Meta wrote:
 It seems like it'd be a lot cheaper and cleaner to just be 
 able to alias
 the parent method.
Yeh, that's the first solution that comes to mind. alias doesn't work here but of course we could change the language.
 Also, it could probably be made a bit simpler with
 opDispatch.
I'd have to see the code, but my intuition is that things could get quite a bit more hairy. Andrei
IMO, using __traits and opDispatch is a fair bit cleaner, and I prefer the syntax of a mixin template to regular mixin. http://dpaste.dzfl.pl/d60498246577
May 03 2015
next sibling parent "Meta" <jared771 gmail.com> writes:
Wow, ParameterTypeTuple even works with ref arguments with no 
problem. That's impressive.
May 03 2015
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/3/15 12:18 AM, Meta wrote:
 On Sunday, 3 May 2015 at 05:49:52 UTC, Andrei Alexandrescu wrote:
 On 5/2/15 10:00 PM, Meta wrote:
 It seems like it'd be a lot cheaper and cleaner to just be able to alias
 the parent method.
Yeh, that's the first solution that comes to mind. alias doesn't work here but of course we could change the language.
 Also, it could probably be made a bit simpler with
 opDispatch.
I'd have to see the code, but my intuition is that things could get quite a bit more hairy. Andrei
IMO, using __traits and opDispatch is a fair bit cleaner, and I prefer the syntax of a mixin template to regular mixin. http://dpaste.dzfl.pl/d60498246577
You're right, that is lovely! I've improved it as follows: mixin template forwardToMember(alias member, methods...) { import std.algorithm : among; import std.traits : ParameterTypeTuple; template opDispatch(string sym) if ((methods.length == 0 || sym.among(methods))) { auto ref opDispatch( ParameterTypeTuple!(__traits(getMember, member, sym)) args) { return __traits(getMember, member, sym)(args); } } } So now ref returns are preserved and the mixin is self-contained (doesn't require imports from the outside). Compared to my solution, this has the advantage that if the child defines a method, it will take precedence over the formatted one. So that allowed me to add a feature: if no methods are specified, all are forwarded. There are a couple of ways in which this could and should be improved, most notably overloads control. Even as is it's pretty darn awesome, Meta could you please make it into a pull request? I think it should go in std.functional. Andrei
May 03 2015
next sibling parent reply "Dicebot" <public dicebot.lv> writes:
On Sunday, 3 May 2015 at 18:54:45 UTC, Andrei Alexandrescu wrote:
 I think it should go in std.functional.
All similar utilities are currently in std.typecons
May 03 2015
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/3/15 12:04 PM, Dicebot wrote:
 On Sunday, 3 May 2015 at 18:54:45 UTC, Andrei Alexandrescu wrote:
 I think it should go in std.functional.
All similar utilities are currently in std.typecons
worksforme -- Andrei
May 03 2015
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/3/15 11:54 AM, Andrei Alexandrescu wrote:
 On 5/3/15 12:18 AM, Meta wrote:
 On Sunday, 3 May 2015 at 05:49:52 UTC, Andrei Alexandrescu wrote:
 On 5/2/15 10:00 PM, Meta wrote:
 It seems like it'd be a lot cheaper and cleaner to just be able to
 alias
 the parent method.
Yeh, that's the first solution that comes to mind. alias doesn't work here but of course we could change the language.
 Also, it could probably be made a bit simpler with
 opDispatch.
I'd have to see the code, but my intuition is that things could get quite a bit more hairy. Andrei
IMO, using __traits and opDispatch is a fair bit cleaner, and I prefer the syntax of a mixin template to regular mixin. http://dpaste.dzfl.pl/d60498246577
You're right, that is lovely! I've improved it as follows: mixin template forwardToMember(alias member, methods...) { import std.algorithm : among; import std.traits : ParameterTypeTuple; template opDispatch(string sym) if ((methods.length == 0 || sym.among(methods))) { auto ref opDispatch( ParameterTypeTuple!(__traits(getMember, member, sym)) args) { return __traits(getMember, member, sym)(args); } } } So now ref returns are preserved and the mixin is self-contained (doesn't require imports from the outside). Compared to my solution, this has the advantage that if the child defines a method, it will take precedence over the formatted one. So that allowed me to add a feature: if no methods are specified, all are forwarded. There are a couple of ways in which this could and should be improved, most notably overloads control. Even as is it's pretty darn awesome, Meta could you please make it into a pull request? I think it should go in std.functional.
Take that back, the opDispatch-based solution has a fatal flaw: doesn't compose properly. For example, in std.allocator it's frequent that allocators stack on top of one another, so one forwards a method call to another one which in turn forwards to another. With code generation this obviously works out of the box because the generated code is identical to what one would write by hand to achieve the same. But the opDispatch-based solution only works one level. Andrei
May 03 2015
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
OK, here's what I have now - two templates that are self contained and 
work well:

The first uses opDispatch to dispatch to a member. The second is a 
simple string function that generates the appropriate code. As discussed 
the latter composes but the former doesn't.

mixin template dispatchToMember(alias member, methods...)
{
     import std.algorithm : among;
     import std.traits : ParameterTypeTuple;
     template opDispatch(string sym)
     if ((methods.length == 0 || sym.among(methods)))
     {
         auto ref opDispatch(ParameterTypeTuple!(__traits(getMember, 
member, sym)) args)
         {
             return __traits(getMember, member, sym)(args);
         }
     }
}

string forwardToMember(string member, string[] funs...)
{
     string result = "    import std.traits : hasMember, 
ParameterTypeTuple;\n";
     foreach (fun; funs)
     {
         result ~= "
     static if (hasMember!(typeof("~member~"), `"~fun~"`))
     auto "~fun~"(ParameterTypeTuple!(typeof("~member~"."~fun~")) args)
     {
         return "~member~"."~fun~"(args);
     }\n";
     }
     return result;
}


Andrei
May 03 2015
parent reply "Meta" <jared771 gmail.com> writes:
On Sunday, 3 May 2015 at 20:25:45 UTC, Andrei Alexandrescu wrote:
 OK, here's what I have now - two templates that are self 
 contained and work well:

 The first uses opDispatch to dispatch to a member. The second 
 is a simple string function that generates the appropriate 
 code. As discussed the latter composes but the former doesn't.
I can't picture what you're talking about. Could you post an example?
May 03 2015
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/3/15 4:20 PM, Meta wrote:
 On Sunday, 3 May 2015 at 20:25:45 UTC, Andrei Alexandrescu wrote:
 OK, here's what I have now - two templates that are self contained and
 work well:

 The first uses opDispatch to dispatch to a member. The second is a
 simple string function that generates the appropriate code. As
 discussed the latter composes but the former doesn't.
I can't picture what you're talking about. Could you post an example?
struct A { void fun(int); } struct B { A a; mixin forwardToMember!(a, "fun"); } struct C { B b; mixin forwardToMember!(b, "fun"); } Andrei
May 03 2015
parent reply "Meta" <jared771 gmail.com> writes:
On Sunday, 3 May 2015 at 23:54:49 UTC, Andrei Alexandrescu wrote:
 On 5/3/15 4:20 PM, Meta wrote:
 On Sunday, 3 May 2015 at 20:25:45 UTC, Andrei Alexandrescu 
 wrote:
 OK, here's what I have now - two templates that are self 
 contained and
 work well:

 The first uses opDispatch to dispatch to a member. The second 
 is a
 simple string function that generates the appropriate code. As
 discussed the latter composes but the former doesn't.
I can't picture what you're talking about. Could you post an example?
struct A { void fun(int); } struct B { A a; mixin forwardToMember!(a, "fun"); } struct C { B b; mixin forwardToMember!(b, "fun"); } Andrei
I still don't get it. Your example works with both dispatchToMember and forwardToMember. What is the difference between these two exactly? auto opDispatch(string sym: foo)(ParemeterTypeTuple!(/*etc*/) args) { return parent.foo(args); } //Automatically generated auto foo(ParameterTypeTuple!(/*etc*/) args) { return parent.foo(args); }
May 03 2015
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/3/15 5:29 PM, Meta wrote:
 On Sunday, 3 May 2015 at 23:54:49 UTC, Andrei Alexandrescu wrote:
 On 5/3/15 4:20 PM, Meta wrote:
 On Sunday, 3 May 2015 at 20:25:45 UTC, Andrei Alexandrescu wrote:
 OK, here's what I have now - two templates that are self contained and
 work well:

 The first uses opDispatch to dispatch to a member. The second is a
 simple string function that generates the appropriate code. As
 discussed the latter composes but the former doesn't.
I can't picture what you're talking about. Could you post an example?
struct A { void fun(int); } struct B { A a; mixin forwardToMember!(a, "fun"); } struct C { B b; mixin forwardToMember!(b, "fun"); } Andrei
I still don't get it. Your example works with both dispatchToMember and forwardToMember. What is the difference between these two exactly? auto opDispatch(string sym: foo)(ParemeterTypeTuple!(/*etc*/) args) { return parent.foo(args); } //Automatically generated auto foo(ParameterTypeTuple!(/*etc*/) args) { return parent.foo(args); }
Hmm, I didn't try it assuming it won't work (thought __traits(getMember, member, sym) would fail with opDispatch). Tried it just now, it does work like a charm. Thanks! Andrei
May 03 2015
parent reply "Meta" <jared771 gmail.com> writes:
On Monday, 4 May 2015 at 01:01:05 UTC, Andrei Alexandrescu wrote:
 Hmm, I didn't try it assuming it won't work (thought 
 __traits(getMember, member, sym) would fail with opDispatch). 
 Tried it just now, it does work like a charm. Thanks!

 Andrei
So is a function that generates a string mixin necessary or not necessary?
May 03 2015
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/3/15 8:13 PM, Meta wrote:
 On Monday, 4 May 2015 at 01:01:05 UTC, Andrei Alexandrescu wrote:
 Hmm, I didn't try it assuming it won't work (thought
 __traits(getMember, member, sym) would fail with opDispatch). Tried it
 just now, it does work like a charm. Thanks!

 Andrei
So is a function that generates a string mixin necessary or not necessary?
I'm not sure. It's a great question. In some ways a string mixin is the straight deal - you get complete control over what you generate, ultimate flexibility, and you get to inspect it if you so want. (BTW I think compiler errors in mixins should either print the failing mixin with line numbers, or make it available as a file). But then the opDispatch solution is more structured and restricted in a good way. I think we need a bit more experience with both to figure out which is the best way to go. More specifically: * forward to enumerated types * distinguish static/non-static methods, and define forwarding appropriately * define properties to forward to member variables * alias member types and templates * other similar stuff I didn't think of, in essence "defer stuff to this member" Andrei
May 03 2015
next sibling parent reply "Temtaime" <temtaime gmail.com> writes:
Will allocator ever be added to phobos as maybe
std.experimental.allocator for a first time ?
May 04 2015
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/4/15 9:31 AM, Temtaime wrote:
 Will allocator ever be added to phobos as maybe
 std.experimental.allocator for a first time ?
Affirmative. Getting Real Close Now. -- Andrei
May 04 2015
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2015-05-04 07:28, Andrei Alexandrescu wrote:

 But then the opDispatch solution is more structured and restricted in a
 good way. I think we need a bit more experience with both to figure out
 which is the best way to go.
I would guess the opDispatch solution doesn't integrate so well with an already present opDispatch (if there's such a use case). -- /Jacob Carlborg
May 04 2015
parent "Meta" <jared771 gmail.com> writes:
On Monday, 4 May 2015 at 19:17:26 UTC, Jacob Carlborg wrote:
 On 2015-05-04 07:28, Andrei Alexandrescu wrote:

 But then the opDispatch solution is more structured and 
 restricted in a
 good way. I think we need a bit more experience with both to 
 figure out
 which is the best way to go.
I would guess the opDispatch solution doesn't integrate so well with an already present opDispatch (if there's such a use case).
Hmm, that's true. I guess it'd be better to start with a mixin-based solution.
May 04 2015
prev sibling parent reply "Meta" <jared771 gmail.com> writes:
On Sunday, 3 May 2015 at 18:54:45 UTC, Andrei Alexandrescu wrote:
 You're right, that is lovely! I've improved it as follows:

 mixin template forwardToMember(alias member, methods...)
 {
     import std.algorithm : among;
     import std.traits : ParameterTypeTuple;
     template opDispatch(string sym)
     if ((methods.length == 0 || sym.among(methods)))
     {
         auto ref opDispatch(
             ParameterTypeTuple!(__traits(getMember, member, 
 sym)) args)
         {
             return __traits(getMember, member, sym)(args);
         }
     }
 }

 So now ref returns are preserved and the mixin is 
 self-contained (doesn't require imports from the outside).

 Compared to my solution, this has the advantage that if the 
 child defines a method, it will take precedence over the 
 formatted one. So that allowed me to add a feature: if no 
 methods are specified, all are forwarded.

 There are a couple of ways in which this could and should be 
 improved, most notably overloads control. Even as is it's 
 pretty darn awesome, Meta could you please make it into a pull 
 request? I think it should go in std.functional.


 Andrei
I have forgotten to mention that I will make a pull request for this ASAP. I have been in the process of moving and starting a new job this week and last so I only have enough time to occasionally post currently.
May 06 2015
parent "Meta" <jared771 gmail.com> writes:
On Wednesday, 6 May 2015 at 22:59:16 UTC, Meta wrote:
 On Sunday, 3 May 2015 at 18:54:45 UTC, Andrei Alexandrescu 
 wrote:
 You're right, that is lovely! I've improved it as follows:

 mixin template forwardToMember(alias member, methods...)
 {
     import std.algorithm : among;
     import std.traits : ParameterTypeTuple;
     template opDispatch(string sym)
     if ((methods.length == 0 || sym.among(methods)))
     {
         auto ref opDispatch(
             ParameterTypeTuple!(__traits(getMember, member, 
 sym)) args)
         {
             return __traits(getMember, member, sym)(args);
         }
     }
 }

 So now ref returns are preserved and the mixin is 
 self-contained (doesn't require imports from the outside).

 Compared to my solution, this has the advantage that if the 
 child defines a method, it will take precedence over the 
 formatted one. So that allowed me to add a feature: if no 
 methods are specified, all are forwarded.

 There are a couple of ways in which this could and should be 
 improved, most notably overloads control. Even as is it's 
 pretty darn awesome, Meta could you please make it into a pull 
 request? I think it should go in std.functional.


 Andrei
I have forgotten to mention that I will make a pull request for this ASAP. I have been in the process of moving and starting a new job this week and last so I only have enough time to occasionally post currently.
http://forum.dlang.org/post/mqnhhbbyctwrfzvbrocg forum.dlang.org
Aug 13 2015
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2015-05-03 06:20, Andrei Alexandrescu wrote:

 Is this a common thing people wanna do? Put in Phobos?
Yes, I would think so. Although, I would prefer a regular template mixin and taking the member as an alias parameter instead of a string, if possible. -- /Jacob Carlborg
May 03 2015
prev sibling parent reply "tcak" <tcak gmail.com> writes:
On Sunday, 3 May 2015 at 00:25:13 UTC, Dicebot wrote:
 Sounds similar to 
 http://dlang.org/phobos/std_typecons.html#.Proxy
Great documentation by the way. I understood what it does with one read: "Make proxy for a."
May 03 2015
parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Sunday, 3 May 2015 at 07:55:30 UTC, tcak wrote:
 On Sunday, 3 May 2015 at 00:25:13 UTC, Dicebot wrote:
 Sounds similar to 
 http://dlang.org/phobos/std_typecons.html#.Proxy
Great documentation by the way. I understood what it does with one read: "Make proxy for a."
http://dlang.org/phobos-prerelease/std_typecons.html#.Proxy
May 03 2015
prev sibling next sibling parent "bioinfornatics" <bioinfornatics fedoraproject.org> writes:
That is not exactly what I requested years ago ---> 
http://forum.dlang.org/thread/1330792937.10754.7.camel jonathan
May 05 2015
prev sibling parent "Laeeth Isharc" <nospamlaeeth nospam.laeeth.com> writes:
On Sunday, 3 May 2015 at 00:19:37 UTC, Andrei Alexandrescu wrote:
 Hey folks,


 So in working with the allocator, a common matter has come 
 again to the fore: I need to forward certain functions to a 
 member.
 So I'm thinking of defining a mixin that would be used like
this: struct FreeTree(ParentAllocator) { ... mixin(forwardIfDefined("parent", "expand", "reallocate", "allocateAll")); }
This was very useful, by the way, with synchronicitous timing. I have various derivative instruments made up of legs. For example an interest rate swap where I pay fixed cash flows on one leg and receive floating cash flows on another. So each swap leg is itself an entity (I made them structs), and there are a collection of such legs (of differing types) comprising a swap (or FRA, FX trade etc). The parent entity (eg a swap with a fixed leg and a float leg; or with two float legs) needs to forward calls to the legs. Eg if you change the notional or maturity of the parent, you probably want to change the notionals of the component legs, although in special cases they might be different. I don't want to muck about with Byzantine inheritance systems I won't likely myself remember in a year (people seem to underrate the value of coherence these days, and fragmentation doesn't help). On the other hand I didn't fancy writing lots of boilerplate code just to forward calls to each leg (or to only one where it made sense). So I modified forwardToMember to forwardToLegs (I need to return a Swap object, since I want to return the Swap for UFCS and the calls to leg methods return the leg itself). setMaturityDate(DateTime maturity( calls fixLeg.setMaturity(maturity) and floatLeg.setMaturity. And setFloatMaturity(DateTime maturity) just calls floatLeg.setMaturity(maturity) - ie the call is remapped to a different name when forwarding, so the user doesn't need to get her hands messy with the nitty gritty of the individual legs. So now all the glue logic is specified in simple arrays that produce the requisite code at compile time. It's easy to see what is going on, maintainable, and the parents are only a very spaced-out 100 lines. So thank you for this, and for all the other nice features in D.
May 09 2015