When writing wrapped types, you might want to forward methods without subtyping. The opDispatch
method lets that happen with very little code.
To forward methods with opDispatch
, we need to execute the following steps:
opDispatch
, which takes variadic arguments: auto opDispatch(string name, T…)(T t)
.return mixin("member." ~ s ~ "(t)");
.The code has to be executed as follows:
class Foo { int test() { return 0; } void nop() {} } struct Wrapped { Foo f; this(Foo f) { this.f = f; } auto opDispatch(string s, T...)(T t) { return mixin("f." ~ s ~ "(t)"); } } void main() { Wrapped w = new Foo(); Int i = w.test(); w.nop(); }
We've been using alias this
throughout the chapter to provide easy access to a wrapped data member or property. However, since this also enables implicit casting to the wrapped type, we don't always want it. Using opDispatch
, we can provide access to a wrapped method without offering subtyping.
The opDispatch
method works similar to the other operator overloads; it is a function with a special name that takes a compile-time string argument. Any time you try to access a nonexistent member with the dot operator, the compiler will try to instantiate opDispatch!"name"
to handle the missing member. Execute the following code:
struct Foo { void test() {} void opDispatch(string s)() {} } Foo f; f.test(); // it finds test in the object, so it is called normally f.disp(); // disp is NOT found in the object, so it tries to call // f.opDispatch!"disp"(); before issuing an error
Once opDispatch
is tried, the arguments are forwarded as well. Since we wanted to capture all possible arguments here to pass on to the wrapped type, we declared opDispatch
as an unrestricted variadic template with T…
. This will accept a list of any length, zero or more, of anything that can be passed as a compile time argument, including types.
Then, the runtime argument list, (T t)
, is also variadic and may consist of several types. It accepts anything (given that the function body compiles).
Just like with RangedInt
we implemented earlier, we will use a string mixin
to concisely implement the forwarding. With the auto
return value, the compile takes care of forwarding returns for us too, including returning void
. The function just works.
Wrapping the type might break some code that would otherwise work thanks to UFCS. Since the mixin
is evaluated in the scope of the wrapped function and UFCS is evaluated at the call site, they may see different functions. The call site can import different modules than the wrapper function, thus enabling some functionality that doesn't work at the wrap site. This is a necessary consequence of using this method.
We can also forward to an array of objects with this same technique:
struct Wrapped { Foo[] fs; // an array instead of just one object this(Foo[] fs) { this.fs = fs; } Wrapped opDispatch(string s, T...)(T t) { foreach(f; fs) // simply loop over them mixin("f." ~ s ~ "(t)"); return this; // for chaining } }
This lets us work with arrays in the same way as jQuery's element collections:
Wrapped getElements() { return Wrapped([array, of, items]); } getElements().addClass("foo").innerHTML("baz");
That code would compile, provided an Element
class is wrapped and provides the individual addClass
and innerHTML
methods. You would not have to write wrapped methods manually to perform the looping.
18.191.147.77