A Beginner's Guide to D/Templates and Generic Programming/Mixins

From Wikibooks, open books for an open world
Jump to navigation Jump to search

D's classes only support single inheritance and interfaces. While interfaces are sufficient to reproduce many of the uses of multiple inheritance, there are some things they don't suffice for. Mixins were originally added to the language to fill in many of these other cases.

Regular Templates[edit | edit source]

Before discussing mixins, it is necessary to discuss the normal syntax for templates. The class and function template syntax discussed in the previous two chapters is actually a shorthand for the regular syntax. A "normal" template looks like this:

template Foo(A, B, C)
{
    A a;
    B b;
    C c;
}

A template consists of a name (Foo), a parameter list (A, B, C), and a series of declarations (a, b, c). When a template is instantiated, the parameters are replaced with the arguments provided by the instantiation. The declarations are placed at the same scope as the template.

alias Foo!(int, real, char[]) F;
F.a = 10;
F.b = 5.8;
F.c = "hello!";

Since the above example is not very useful, here's what the long form of the "min" function from the previous section looks like.

template min(T)
{
    T min(T t1, T t2)
    {
        if (t1 < t2) return t1;
        else return t2;
    }
}

This also shows another interesting feature of D's templates. If a template contains a single declaration, and that declaration has the same name as the template, then one can refer to that single member implicitly. Combined with implicit function template instantiation (discussed in the previous section), the following are all equivalent:

min!(int).min(1, 2); // long form
min!(int)(1, 2); // with the single-element rule
min(1, 2); // with IFTI

Mixins[edit | edit source]

Normally, when a template is instantiated, its declarations are inserted into whatever scope the template exists in. With a mixin, the declarations may be inserted into the current scope. A trivial example looks like this:

template Foo()
{
    int i;
    char[] s;
}
struct S
{
    mixin Foo;
}

(Note that if the template's parameter list is empty, then the mixin may elide the !().) The result is that the struct S has two members: int i and char[] s.

Mixins may also be named, allowing the same mixin to be mixed-in to the current scope more than once. Here's a more complex example.

template Writer(T)
{
    void write(T t)
    {
        writefln(t);
    }
}
struct S
{
    mixin Writer!(int) IntWriter;
    mixin Writer!(char[]) StrWriter;
}
S s;
s.IntWriter.write(12);
s.StrWriter.write("hello!");