Forwarding methods with opDispatch

When writing wrapped types, you might want to forward methods without subtyping. The opDispatch method lets that happen with very little code.

How to do it…

To forward methods with opDispatch, we need to execute the following steps:

  1. Write the wrapped struct.
  2. Write a method called opDispatch, which takes variadic arguments: auto opDispatch(string name, T…)(T t).
  3. In the body, write 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();
}

How it works…

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.

Note

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.147.77