www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How to dispatch a class function for an object accessed by handle?

reply wjoe <invalid example.org> writes:
Consider a Factory that creates instances of various different 
resource object instances, all of which have a common interface, 
and returns a handle to them.

class Factory
{
    struct Handle{}

    Handle create(R: Resource, ARGS...)(ARGS args)
    {
       auto r = new R(args);
       //...
       return handle;
     }
}

auto f = new Factory;
auto wallpaperhandle = f.create!Bitmap(...);


In order to be able do something with these resources there's a 
function to get the resource instance from a handle:

    auto get(R: Resource)(Handle h) {...};

auto bitmap = f.get!Bitmap(wallpaperhandle);
auto size = bitmap.getSize();
if (condition)
    bitmap.scaleByFactor(2);

I don't like this approach because it exposes the instance 
directly.
What I want instead is something like this

auto size = f.dispatch!(Bitmap, Bitmap.getSize)(wallpaperhandle);
if (condition)
    f.dispatch!(Bitmap, Bitmap.scaleByFactor)(wallpaperhandle, 2);

Implement this for free functions i would do something like this

void dispatch(alias fn, ARGS...)(Handle handle, ARGS args)
{
    fn(handle, args);
}


but how can I call fn in the context of an object instance?

class Factory {
   auto ref dispatch(R: Resource, alias fn, ARGS ...)(Handle 
handle, ARGS args)
   {
      auto r = get!R(handle);
      assert(stuff is valid);

      return r.fn(args); // How can I call class function 'fn' 
using r ?
   }
}

mixin(something) comes to mind, but are there better options ?
Mar 05 2020
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 3/5/20 9:24 AM, wjoe wrote:

 
 but how can I call fn in the context of an object instance?
You could do it with delegates. But it's ugly: import std.stdio; class C { void foo() { writeln("Yup");} } void main() { alias f = C.foo; auto c = new C; void delegate() dg; dg.funcptr = &f; dg.ptr = cast(void*)c; dg(); // prints "Yup" } I don't know of a way to call f with c aside from this. -Steve
Mar 05 2020
parent wjoe <invalid example.org> writes:
On Thursday, 5 March 2020 at 14:46:24 UTC, Steven Schveighoffer 
wrote:
 On 3/5/20 9:24 AM, wjoe wrote:

 
 but how can I call fn in the context of an object instance?
You could do it with delegates. But it's ugly: import std.stdio; class C { void foo() { writeln("Yup");} } void main() { alias f = C.foo; auto c = new C; void delegate() dg; dg.funcptr = &f; dg.ptr = cast(void*)c; dg(); // prints "Yup" } I don't know of a way to call f with c aside from this. -Steve
I have an ugly implementation with a one liner mixin and I don't like it. Your solution looks interesting but I think that's more code than my current solution. Thanks for your reply though.
Mar 05 2020
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 5 March 2020 at 14:24:33 UTC, wjoe wrote:
 Implement this for free functions i would do something like this

 void dispatch(alias fn, ARGS...)(Handle handle, ARGS args)
Why do you need an `alias fn` like that? My suggestion would be to just use the `opDispatch` magic method that gives you a string, then `__traits(getMember, obj, memberName)(args)` to call it. But if you specifically need the alias param that won't work as well. (You could still do `__traits(getMember, obj, __traits(identifier, fn))` though, so it isn't ruled out entirely, just not as nice. That is also more likely to break with overloads btw) struct Handle { private Whatever obj; auto opDispatch(string name, Args...)(Args args) { return __traits(getMember, obj, name)(args); } } And the usage would look like: auto size = f.getSize(wallpaperhandle); assuming the Handle knows how to store Whatever without being told what it is again at the call site (e.g. if you actually use an `interface` internally, or an encapsulated tagged union or whatever). If you do need to tell it what it is, a two-level function gives that opportunity: template opDispatch(string name) { auto opDispatch(T, Args...)(Args args) { auto obj = cast(T) o; // or whatever you do to convert return __traits(getMember, obj, name)(args); } } then the usage looks like auto size = f.getSize!Bitmap(wallpaperhandle); NOTE: opDispatch suppresses internal compile errors, it will just say "no such property whatever". you can explicitly instantiate with `f.opDispatch!"whatever` to help see better errors. But it depends on what exactly you are doing.
Mar 05 2020
next sibling parent wjoe <invalid example.org> writes:
On Thursday, 5 March 2020 at 18:33:41 UTC, Adam D. Ruppe wrote:
 On Thursday, 5 March 2020 at 14:24:33 UTC, wjoe wrote:
 Implement this for free functions i would do something like 
 this

 void dispatch(alias fn, ARGS...)(Handle handle, ARGS args)
Why do you need an `alias fn` like that? My suggestion would be to just use the `opDispatch` magic method that gives you a string, then `__traits(getMember, obj, memberName)(args)` to call it. But if you specifically need the alias param that won't work as well. (You could still do `__traits(getMember, obj, __traits(identifier, fn))` though, so it isn't ruled out entirely, just not as nice. That is also more likely to break with overloads btw) struct Handle { private Whatever obj; auto opDispatch(string name, Args...)(Args args) { return __traits(getMember, obj, name)(args); } } And the usage would look like: auto size = f.getSize(wallpaperhandle); assuming the Handle knows how to store Whatever without being told what it is again at the call site (e.g. if you actually use an `interface` internally, or an encapsulated tagged union or whatever). If you do need to tell it what it is, a two-level function gives that opportunity: template opDispatch(string name) { auto opDispatch(T, Args...)(Args args) { auto obj = cast(T) o; // or whatever you do to convert return __traits(getMember, obj, name)(args); } } then the usage looks like auto size = f.getSize!Bitmap(wallpaperhandle); NOTE: opDispatch suppresses internal compile errors, it will just say "no such property whatever". you can explicitly instantiate with `f.opDispatch!"whatever` to help see better errors. But it depends on what exactly you are doing.
Thanks for your reply:) I don't need an alias at all. I was trying to figure something out with opDispatch first but something like __traits(getMember, obj, name)(args); never occurred to me. Awesome! The handle knows whether or not it's valid and where to find the object and it only makes sense in the context of the factory that made it. The template opDispatch looks like what I was looking for :)
Mar 05 2020
prev sibling parent reply wjoe <invalid example.com> writes:
On Thursday, 5 March 2020 at 18:33:41 UTC, Adam D. Ruppe wrote:
 On Thursday, 5 March 2020 at 14:24:33 UTC, wjoe wrote:

 [...]

 template opDispatch(string name) {
     auto opDispatch(T, Args...)(Args args) {
        ...
     }
 }

 [...]


 NOTE: opDispatch suppresses internal compile errors, it will 
 just say "no such property whatever". you can explicitly 
 instantiate with `f.opDispatch!"whatever` to help see better 
 errors.
Follow-up question: Calling f.whatever!SomeResource(...); works no problem. However I can't figure out how to call a function by explicitly instantiating opDispatch. Since f.opDispatch!"load"(handle, "wallpaper.png"); doesn't compile, I refreshed my memory about the shortcut syntax and the eponymous syntax and the way I read it is that this is a template of a template. So I tried this: f.opDispatch!"load".opDispatch!Bitmap(handle, "path/to/wallpaper.png"); But this doesn't compile either and errors out with: Error: Cannot resolve type for f.opDispatch(T, ARGS...)(ResourceHandle handle, ARGS args) I don't understand this error message. Which type can't be resolved? Is there a way to look at output of what the compiler generates for f.whatever!SomeResource(...); ?
Mar 06 2020
next sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 6 March 2020 at 11:51:54 UTC, wjoe wrote:
 I don't understand this error message. Which type can't be 
 resolved?
I.... don't know. It works if you rename the inner one but it doesn't like eponymous templates like this. I suspect either the spec subtly doesn't allow it or a compiler bug. I think the type it is referring to is the `this` type. You can work around with an alias: // test rig import std.stdio; struct A { template opDispatch(string name) { auto opDispatch(T, Args...)(Args args) { writeln(name, ".", T.stringof, "(", args, ")"); } } } // workaround void main() { A a; alias helper = a.opDispatch!("foo"); a.helper!(int)(5, "omg"); } So the helper does one level, then the next level is done on the next line to avoid the stupid "multiple ! not allowed". You need to specify the `a` again to avoid `need this for...` due to how aliases are kinda weird. Huge hassle to use but if just doing it temporarily to debug it can be livable.
 Is there a way to look at output of what the compiler generates 
 for f.whatever!SomeResource(...); ?
-vcg-ast or something like to dmd but i never use it since there's TONS of spam in a file called `yourfile.d.cg`
Mar 06 2020
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 3/6/20 6:51 AM, wjoe wrote:
 On Thursday, 5 March 2020 at 18:33:41 UTC, Adam D. Ruppe wrote:
 On Thursday, 5 March 2020 at 14:24:33 UTC, wjoe wrote:

 [...]

 template opDispatch(string name) {
     auto opDispatch(T, Args...)(Args args) {
        ...
     }
 }

 [...]


 NOTE: opDispatch suppresses internal compile errors, it will just say 
 "no such property whatever". you can explicitly instantiate with 
 `f.opDispatch!"whatever` to help see better errors.
Follow-up question: Calling f.whatever!SomeResource(...); works no problem. However I can't figure out how to call a function by explicitly instantiating opDispatch. Since f.opDispatch!"load"(handle, "wallpaper.png"); doesn't compile, I refreshed my memory about the shortcut syntax and the eponymous syntax and the way I read it is that this is a template of a template. So I tried this: f.opDispatch!"load".opDispatch!Bitmap(handle, "path/to/wallpaper.png");
This doesn't work, because an eponymous template does not provide access to the internals of the template.
 
 But this doesn't compile either and errors out with:
 Error: Cannot resolve type for f.opDispatch(T, ARGS...)(ResourceHandle 
 handle, ARGS args)
 
 I don't understand this error message. Which type can't be resolved?
 
 Is there a way to look at output of what the compiler generates for 
 f.whatever!SomeResource(...); ?
You can use -vcg-ast, but this isn't necessarily going to be compilable code. D doesn't allow chained instantiation (i.e. (A!B)!C), so you need to use either a mixin or a helper: import std.meta; enum fname = "load"; Instantiate!(f.opDispatch!fname, Bitmap)("path/to/wallpaper.png") or mixin("f." ~ fname ~ "!(Bitmap)(...);"); I'm assuming fname is given to you as a compile-time string and that's why you'd need to run opDispatch manually. -Steve
Mar 06 2020
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 3/6/20 8:55 AM, Steven Schveighoffer wrote:
 Instantiate!(f.opDispatch!fname, Bitmap)("path/to/wallpaper.png")
I realized, this doesn't work. Because f.opDispatch is a `this` call, but is not called that way in this case. Adam's way doesn't work either, because the call doesn't use the alias, but just instantiates opDispatch with the new name! I think the only solution might be the mixin. -Steve
Mar 06 2020
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 6 March 2020 at 14:05:55 UTC, Steven Schveighoffer 
wrote:
 Adam's way doesn't work either, because the call doesn't use 
 the alias, but just instantiates opDispatch with the new name!'
oh yikes, how did I not notice that?! so yeah just kinda screwed. I'd probably suggest at tis point having the opDispatch be a trivial implementation that just forwards to another named method. struct A { template opDispatch(string name) { auto opDispatch(T, Args...)(Args args) { return other_name!(name, T, Args)(args); } } auto other_name(string name, T, Args...)(Args args) { // real implementation } } and then to test it externally you do a.other_name!("whatever", Bitmap)(args, here);
Mar 06 2020
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 3/6/20 9:14 AM, Adam D. Ruppe wrote:
 On Friday, 6 March 2020 at 14:05:55 UTC, Steven Schveighoffer wrote:
 Adam's way doesn't work either, because the call doesn't use the 
 alias, but just instantiates opDispatch with the new name!'
oh yikes, how did I not notice that?! so yeah just kinda screwed. I'd probably suggest at tis point having the opDispatch be a trivial implementation that just forwards to another named method. struct A {   template opDispatch(string name) {      auto opDispatch(T, Args...)(Args args) {            return other_name!(name, T, Args)(args);      }   }
Do this instead, I think this will work and avoids an extra call (and having to do the argument plumbing that inevitably comes with this kind of wrapping): template opDispatch(string name) { alias opdispatch(T) = other_name!(name, T); } template other_name(string name, T) { auto other_name(Args...)(Args args) { // real implementation } } -Steve
Mar 06 2020
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 3/6/20 9:42 AM, Steven Schveighoffer wrote:
 alias opdispatch(T) = other_name!(name, T);
And obviously, this should be opDispatch with a capital D ! -Steve
Mar 06 2020
prev sibling parent reply wjoe <invalid example.com> writes:
On Friday, 6 March 2020 at 14:14:04 UTC, Adam D. Ruppe wrote:
 On Friday, 6 March 2020 at 14:05:55 UTC, Steven Schveighoffer 
 wrote:
 Adam's way doesn't work either, because the call doesn't use 
 the alias, but just instantiates opDispatch with the new name!'
oh yikes, how did I not notice that?! so yeah just kinda screwed. I'd probably suggest at tis point having the opDispatch be a trivial implementation that just forwards to another named method. struct A { template opDispatch(string name) { auto opDispatch(T, Args...)(Args args) { return other_name!(name, T, Args)(args); } } auto other_name(string name, T, Args...)(Args args) { // real implementation } } and then to test it externally you do a.other_name!("whatever", Bitmap)(args, here);
This isn't the worst thing to use and since it's just for testing it's fine. I came up with a similar interface like a.other_name!("whatever", Bitmap)(args, here); after discarding .opDispatch() and I called the thing .dispatch(T, string fn, ARGS...)(...). But didn't like the string part and that's when I introduced the alias fn because I figured maybe it's possible to do something like: factory.dispatch!(Bitmap.load)(handle, path); and get the Bitmap part from that alias and hence save the duplicate Bitmap type in factory.dispatch!(Bitmap, Bitmap.load)(...); Anyways thanks for your help.
Mar 06 2020
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 6 March 2020 at 15:05:56 UTC, wjoe wrote:
 But didn't like the string part and that's when I introduced 
 the alias fn because I figured maybe it's possible to do 
 something like:
   factory.dispatch!(Bitmap.load)(handle, path);
 and get the Bitmap part from that alias and hence save the 
 duplicate Bitmap type in factory.dispatch!(Bitmap, 
 Bitmap.load)(...);
ooh you can do that. on the alias, use __traits(identifier) to get thef ucntion name and __traits(parent) to get the class name! then it reduces to what we already wrote.
Mar 06 2020
parent wjoe <invalid example.com> writes:
On Friday, 6 March 2020 at 15:19:39 UTC, Adam D. Ruppe wrote:
 On Friday, 6 March 2020 at 15:05:56 UTC, wjoe wrote:
 But didn't like the string part and that's when I introduced 
 the alias fn because I figured maybe it's possible to do 
 something like:
   factory.dispatch!(Bitmap.load)(handle, path);
 and get the Bitmap part from that alias and hence save the 
 duplicate Bitmap type in factory.dispatch!(Bitmap, 
 Bitmap.load)(...);
ooh you can do that. on the alias, use __traits(identifier) to get thef ucntion name and __traits(parent) to get the class name! then it reduces to what we already wrote.
Awesome! I read the docs up and down but I couldn't figure it out. Thank you!
Mar 06 2020
prev sibling parent wjoe <invalid example.com> writes:
On Friday, 6 March 2020 at 13:55:25 UTC, Steven Schveighoffer 
wrote:
 On 3/6/20 6:51 AM, wjoe wrote:
 On Thursday, 5 March 2020 at 18:33:41 UTC, Adam D. Ruppe wrote:
 On Thursday, 5 March 2020 at 14:24:33 UTC, wjoe wrote:

 [...]

 template opDispatch(string name) {
     auto opDispatch(T, Args...)(Args args) {
        ...
     }
 }

 [...]


 NOTE: opDispatch suppresses internal compile errors, it will 
 just say "no such property whatever". you can explicitly 
 instantiate with `f.opDispatch!"whatever` to help see better 
 errors.
Follow-up question: Calling f.whatever!SomeResource(...); works no problem. However I can't figure out how to call a function by explicitly instantiating opDispatch. Since f.opDispatch!"load"(handle, "wallpaper.png"); doesn't compile, I refreshed my memory about the shortcut syntax and the eponymous syntax and the way I read it is that this is a template of a template. So I tried this: f.opDispatch!"load".opDispatch!Bitmap(handle, "path/to/wallpaper.png");
This doesn't work, because an eponymous template does not provide access to the internals of the template.
 
 But this doesn't compile either and errors out with:
 Error: Cannot resolve type for f.opDispatch(T, 
 ARGS...)(ResourceHandle handle, ARGS args)
 
 I don't understand this error message. Which type can't be 
 resolved?
 
 Is there a way to look at output of what the compiler 
 generates for f.whatever!SomeResource(...); ?
You can use -vcg-ast, but this isn't necessarily going to be compilable code. D doesn't allow chained instantiation (i.e. (A!B)!C), so you need to use either a mixin or a helper: import std.meta; enum fname = "load"; Instantiate!(f.opDispatch!fname, Bitmap)("path/to/wallpaper.png") or mixin("f." ~ fname ~ "!(Bitmap)(...);"); I'm assuming fname is given to you as a compile-time string and that's why you'd need to run opDispatch manually. -Steve
I tried Instantiate this morning after I found a reply you made to someone else who was trying to chain instantiate. But for the reason you stated it didn't work. Funny you mention: mixin("f." ~ fname ~ "!(Bitmap)(...);"); Because that's like my initial implementation :) As for the the command line switch. My plan wasn't to copy paste. Sometimes I have a hard time to comprehend results because it's like I've put water, flour and eggs on the table. Then I'm presented with a loaf of bread and I'm baffled. Then I want to understand what happened between putting the ingredients on the table and the ready baked loaf. Anyways, thanks for your answers.
Mar 06 2020