Programming in D – Tutorial and Reference
Ali Çehreli

Other D Resources

More Templates

We have seen the power and convenience of templates in the Templates chapter. A single templated definition of an algorithm or a data structure is sufficient to use that definition for multiple types.

That chapter covered only the most common uses of templates: function, struct, and class templates and their uses with type template parameters. In this chapter we will see templates in more detail. Before going further, I recommend that you review at least the summary section of that chapter.

The shortcut syntax

In addition to being powerful, D templates are easy to define and use and they are very readable. Defining a function, struct, or class template is as simple as providing a template parameter list:

T twice(T)(T value) {
    return 2 * value;
}

class Fraction(T) {
    T numerator;
    T denominator;

    // ...
}

Template definitions like the ones above are taking advantage of D's shortcut template syntax.

In their full syntax, templates are defined by the template keyword. The equivalents of the two template definitions above are the following:

template twice(T) {
    T twice(T value) {
        return 2 * value;
    }
}

template Fraction(T) {
    class Fraction {
        T numerator;
        T denominator;

        // ...
    }
}

Although most templates are defined by the shortcut syntax, the compiler always uses the full syntax. We can imagine the compiler applying the following steps to convert a shortcut syntax to its full form behind the scenes:

  1. Wrap the definition with a template block.
  2. Give the same name to that block.
  3. Move the template parameter list to the template block.

The full syntax that is arrived after those steps is called an eponymous template, which the programmer can define explicitly as well. We will see eponymous templates later below.

Template name space

It is possible to have more than one definition inside a template block. The following template contains both a function and a struct definition:

template MyTemplate(T) {
    T foo(T value) {
        return value / 3;
    }

    struct S {
        T member;
    }
}

Instantiating the template for a specific type instantiates all of the definitions inside the block. The following code instantiates the template for int and double:

    auto result = MyTemplate!int.foo(42);
    writeln(result);

    auto s = MyTemplate!double.S(5.6);
    writeln(s.member);

A specific instantiation of a template introduces a name space. The definitions that are inside an instantiation can be used by that name. However, if these names are too long, it is always possible to use aliases as we have seen in the alias chapter:

    alias MyStruct = MyTemplate!dchar.S;

// ...

    auto o = MyStruct('a');
    writeln(o.member);
Eponymous templates

Eponymous templates are template blocks that contain a definition that has the same name as that block. In fact, each shortcut template syntax is the shortcut of an eponymous template.

As an example, assume that a program needs to qualify types that are larger than 20 bytes as too large. Such a qualification can be achieved by a constant bool value inside a template block:

template isTooLarge(T) {
    enum isTooLarge = T.sizeof > 20;
}

Note how the names of both the template block and its only definition are the same. This eponymous template is used by the shortcut syntax instead of the whole isTooLarge!int.isTooLarge:

    writeln(isTooLarge!int);

The highlighted part above is the same as the bool value inside the block. Since the size of int is less than 20, the output of the code would be false.

That eponymous template can be defined by the shortcut syntax as well:

enum isTooLarge(T) = T.sizeof > 20;

A common use of eponymous templates is defining type aliases depending on certain conditions. For example, the following eponymous template picks the larger of two types by setting an alias to it:

template LargerOf(A, B) {
    static if (A.sizeof < B.sizeof) {
        alias LargerOf = B;

    } else {
        alias LargerOf = A;
    }
}

Since long is larger than int (8 bytes versus 4 bytes), LargerOf!(int, long) would be the same as the type long. Such templates are especially useful in other templates where the two types are template parameters themselves (or depend on template parameters):

// ...

/* The return type of this function is the larger of its two
 * template parameters: Either type A or type B. */
auto calculate(A, B)(A a, B b) {
    LargerOf!(A, B) result;
    // ...
    return result;
}

void main() {
    auto f = calculate(1, 2L);
    static assert(is (typeof(f) == long));
}
Kinds of templates
Function, class, and struct templates

We have already covered function, class, and struct templates in the Templates chapter and we have seen many examples of them since then.

Member function templates

struct and class member functions can be templates as well. For example, the following put() member function template would work with any parameter type as long as that type is compatible with the operations inside the template (for this specific template, it should be convertible to string):

class Sink {
    string content;

    void put(T)(auto ref const T value) {
        import std.conv;
        content ~= value.to!string;
    }
}

However, as templates can have potentially infinite number of instantiations, they cannot be virtual functions because the compiler cannot know which specific instantiations of a template to include in the interface. (Accordingly, the abstract keyword cannot be used either.)

For example, although the presence of the put() template in the following subclass may give the impression that it is overriding a function, it actually hides the put name of the superclass (see name hiding in the alias chapter):

class Sink {
    string content;

    void put(T)(auto ref const T value) {
        import std.conv;
        content ~= value.to!string;
    }
}

class SpecialSink : Sink {
    /* The following template definition does not override
     * the template instances of the superclass; it hides
     * those names. */
    void put(T)(auto ref const T value) {
        import std.string;
        super.put(format("{%s}", value));
    }
}

void fillSink(Sink sink) {
    /* The following function calls are not virtual. Because
     * parameter 'sink' is of type 'Sink', the calls will
     * always be dispatched to Sink's 'put' template
     * instances. */

    sink.put(42);
    sink.put("hello");
}

void main() {
    auto sink = new SpecialSink();
    fillSink(sink);

    import std.stdio;
    writeln(sink.content);
}

As a result, although the object actually is a SpecialSink, both of the calls inside fillSink() are dispatched to Sink and the content does not contain the curly brackets that SpecialSink.put() inserts:

42hello    ← Sink's behavior, not SpecialSink's
Union templates

Union templates are similar to struct templates. The shortcut syntax is available for them as well.

As an example, let's design a more general version of the IpAdress union that we saw in the Unions chapter. There, the value of the IPv4 address was kept as a uint member in that earlier version of IpAdress, and the element type of the segment array was ubyte:

union IpAddress {
    uint value;
    ubyte[4] bytes;
}

The bytes array provided easy access to the four segments of the IPv4 address.

The same concept can be implemented in a more general way as the following union template:

union SegmentedValue(ActualT, SegmentT) {
    ActualT value;
    SegmentT[/* number of segments */] segments;
}

That template would allow specifying the types of the value and its segments freely.

The number of segments that are needed depends on the types of the actual value and the segments. Since an IPv4 address has four ubyte segments, that value was hard-coded as 4 in the earlier definition of IpAddress. For the SegmentedValue template, the number of segments must be computed at compile time when the template is instantiated for the two specific types.

The following eponymous template takes advantage of the .sizeof properties of the two types to calculate the number of segments needed:

template segmentCount(ActualT, SegmentT) {
    enum segmentCount = ((ActualT.sizeof + (SegmentT.sizeof - 1))
                         / SegmentT.sizeof);
}

The shortcut syntax may be more readable:

enum segmentCount(ActualT, SegmentT) =
    ((ActualT.sizeof + (SegmentT.sizeof - 1))
     / SegmentT.sizeof);

Note: The expression SegmentT.sizeof - 1 is for when the sizes of the types cannot be divided evenly. For example, when the actual type is 5 bytes and the segment type is 2 bytes, even though a total of 3 segments are needed, the result of the integer division 5/2 would incorrectly be 2.

The definition of the union template is now complete:

union SegmentedValue(ActualT, SegmentT) {
    ActualT value;
    SegmentT[segmentCount!(ActualT, SegmentT)] segments;
}

Instantiation of the template for uint and ubyte would be the equivalent of the earlier definition of IpAddress:

import std.stdio;

void main() {
    auto address = SegmentedValue!(uint, ubyte)(0xc0a80102);

    foreach (octet; address.segments) {
        write(octet, ' ');
    }
}

The output of the program is the same as the one in the Unions chapter:

2 1 168 192

To demonstrate the flexibility of this template, let's imagine that it is required to access the parts of the IPv4 address as two ushort values. It would be as easy as providing ushort as the segment type:

    auto address = SegmentedValue!(uint, ushort)(0xc0a80102);

Although unusual for an IPv4 address, the output of the program would consist of two ushort segment values:

258 49320
Interface templates

Interface templates provide flexibility on the types that are used on an interface (as well as values such as sizes of fixed-length arrays and other features of an interface).

Let's define an interface for colored objects where the type of the color is determined by a template parameter:

interface ColoredObject(ColorT) {
    void paint(ColorT color);
}

That interface template requires that its subtypes must define the paint() function but it leaves the type of the color flexible.

A class that represents a frame on a web page may choose to use a color type that is represented by its red, green, and blue components:

struct RGB {
    ubyte red;
    ubyte green;
    ubyte blue;
}

class PageFrame : ColoredObject!RGB {
    void paint(RGB color) {
        // ...
    }
}

On the other hand, a class that uses the frequency of light can choose a completely different type to represent color:

alias Frequency = double;

class Bulb : ColoredObject!Frequency {
    void paint(Frequency color) {
        // ...
    }
}

However, as explained in the Templates chapter, "every template instantiation yields a distinct type". Accordingly, the interfaces ColoredObject!RGB and ColoredObject!Frequency are unrelated interfaces, and PageFrame and Bulb are unrelated classes.

Kinds of template parameters

The template parameters that we have seen so far have all been type parameters. So far, parameters like T and ColorT all represented types. For example, T meant int, double, Student, etc. depending on the instantiation of the template.

There are other kinds of template parameters: value, this, alias, and tuple.

Type template parameters

This section is only for completeness. All of the templates that we have seen so far had type parameters.

Value template parameters

Value template parameters allow flexibility on certain values used in the template implementation.

Since templates are a compile-time feature, the values for the value template parameters must be known at compile time; values that must be calculated at run time cannot be used.

To see the advantage of value template parameters, let's start with a set of structs representing geometric shapes:

struct Triangle {
    Point[3] corners;
// ...
}

struct Rectangle {
    Point[4] corners;
// ...
}

struct Pentagon {
    Point[5] corners;
// ...
}

Let's assume that other member variables and member functions of those types are exactly the same and that the only difference is the value that determines the number of corners.

Value template parameters help in such cases. The following struct template is sufficient to represent all of the types above and more:

struct Polygon(size_t N) {
    Point[N] corners;
// ...
}

The only template parameter of that struct template is a value named N of type size_t. The value N can be used as a compile-time constant anywhere inside the template.

That template is flexible enough to represent shapes of any sides:

    auto centagon = Polygon!100();

The following aliases correspond to the earlier struct definitions:

alias Triangle = Polygon!3;
alias Rectangle = Polygon!4;
alias Pentagon = Polygon!5;

// ...

    auto triangle = Triangle();
    auto rectangle = Rectangle();
    auto pentagon = Pentagon();

The type of the value template parameter above was size_t. As long as the value can be known at compile time, a value template parameter can be of any type: a fundamental type, a struct type, an array, a string, etc.

struct S {
    int i;
}

// Value template parameter of struct S
void foo(S s)() {
    // ...
}

void main() {
    foo!(S(42))();    // Instantiating with literal S(42)
}

The following example uses a string template parameter to represent an XML tag to produce a simple XML output:

For example, an XML tag representing location 42 would be printed as <location>42</location>.

import std.string;

class XmlElement(string tag) {
    double value;

    this(double value) {
        this.value = value;
    }

    override string toString() const {
        return format("<%s>%s</%s>", tag, value, tag);
    }
}

Note that the template parameter is not about a type that is used in the implementation of the template, rather it is about a string value. That value can be used anywhere inside the template as a string.

The XML elements that a program needs can be defined as aliases as in the following code:

alias Location = XmlElement!"location";
alias Temperature = XmlElement!"temperature";
alias Weight = XmlElement!"weight";

void main() {
    Object[] elements;

    elements ~= new Location(1);
    elements ~= new Temperature(23);
    elements ~= new Weight(78);

    writeln(elements);
}

The output:

[<location>1</location>, <temperature>23</temperature>, <weight>78</weight>]

Value template parameters can have default values as well. For example, the following struct template represents points in a multi-dimensional space where the default number of dimensions is 3:

struct Point(T, size_t dimension = 3) {
    T[dimension] coordinates;
}

That template can be used without specifying the dimension template parameter:

    Point!double center;    // a point in 3-dimensional space

The number of dimensions can still be specified when needed:

    Point!(int, 2) point;   // a point on a surface

We have seen in the Variable Number of Parameters chapter how special keywords work differently depending on whether they appear inside code or as default function arguments.

Similarly, when used as default template arguments, the special keywords refer to where the template is instantiated at, not where the keywords appear:

import std.stdio;

void func(T,
          string functionName = __FUNCTION__,
          string file = __FILE__,
          size_t line = __LINE__)(T parameter) {
    writefln("Instantiated at function %s at file %s, line %s.",
             functionName, file, line);
}

void main() {
    func(42);    // ← line 12
}

Although the special keywords appear in the definition of the template, their values refer to main(), where the template is instantiated at:

Instantiated at function deneme.main at file deneme.d, line 12.

We will use __FUNCTION__ below in a multi-dimensional operator overloading example.

this template parameters for member functions

Member functions can be templates as well. Their template parameters have the same meanings as other templates.

However, unlike other templates, member function template parameters can also be this parameters. In that case, the identifier that comes after the this keyword represents the exact type of the this reference of the object. (this reference means the object itself, as is commonly written in constructors as this.member = value.)

struct MyStruct(T) {
    void foo(this OwnType)() const {
        writeln("Type of this object: ", OwnType.stringof);
    }
}

The OwnType template parameter is the actual type of the object that the member function is called on:

    auto m = MyStruct!int();
    auto c = const(MyStruct!int)();
    auto i = immutable(MyStruct!int)();

    m.foo();
    c.foo();
    i.foo();

The output:

Type of this object: MyStruct!int
Type of this object: const(MyStruct!int)
Type of this object: immutable(MyStruct!int)

As you can see, the type includes the corresponding type of T as well as the type qualifiers like const and immutable.

The struct (or class) need not be a template. this template parameters can appear on member function templates of non-templated types as well.

this template parameters can be useful in template mixins as well, which we will see two chapters later.

alias template parameters

alias template parameters can correspond to any symbol or expression that is used in the program. The only constraint on such a template argument is that the argument must be compatible with its use inside the template.

filter() and map() use alias template parameters to determine the operations that they execute.

Let's see a simple example on a struct template that is for modifying an existing variable. The struct template takes the variable as an alias parameter:

struct MyStruct(alias variable) {
    void set(int value) {
        variable = value;
    }
}

The member function simply assigns its parameter to the variable that the struct template is instantiated with. That variable must be specified during the instantiation of the template:

    int x = 1;
    int y = 2;

    auto object = MyStruct!x();
    object.set(10);
    writeln("x: ", x, ", y: ", y);

In that instantiation, the variable template parameter corresponds to the variable x:

x: 10, y: 2

Conversely, MyStruct!y instantiation of the template would associate variable with y.

Let's now have an alias parameter that represents a callable entity, similar to filter() and map():

void caller(alias func)() {
    write("calling: ");
    func();
}

As seen by the () parentheses, caller() uses its template parameter as a function. Additionally, since the parentheses are empty, it must be legal to call the function without specifying any arguments.

Let's have the following two functions that match that description. They can both represent func because they can be called as func() in the template:

void foo() {
    writeln("foo called.");
}

void bar() {
    writeln("bar called.");
}

Those functions can be used as the alias parameter of caller():

    caller!foo();
    caller!bar();

The output:

calling: foo called.
calling: bar called.

As long as it matches the way it is used in the template, any symbol can be used as an alias parameter. As a counter example, using an int variable with caller() would cause a compilation error:

    int variable;
    caller!variable();    // ← compilation ERROR

The compilation error indicates that the variable does not match its use in the template:

Error: function expected before (), not variable of type int

Although the mistake is with the caller!variable instantiation, the compilation error necessarily points at func() inside the caller() template because from the point of view of the compiler the error is with trying to call variable as a function. One way of dealing with this issue is to use template constraints, which we will see below.

If the variable supports the function call syntax perhaps because it has an opCall() overload or it is a function literal, it would still work with the caller() template. The following example demonstrates both of those cases:

class C {
    void opCall() {
        writeln("C.opCall called.");
    }
}

// ...

    auto o = new C();
    caller!o();

    caller!({ writeln("Function literal called."); })();

The output:

calling: C.opCall called.
calling: Function literal called.

alias parameters can be specialized as well. However, they have a different specialization syntax. The specialized type must be specified between the alias keyword and the name of the parameter:

import std.stdio;

void foo(alias variable)() {
    writefln("The general definition is using '%s' of type %s.",
             variable.stringof, typeof(variable).stringof);
}

void foo(alias int i)() {
    writefln("The int specialization is using '%s'.",
             i.stringof);
}

void foo(alias double d)() {
    writefln("The double specialization is using '%s'.",
             d.stringof);
}

void main() {
    string name;
    foo!name();

    int count;
    foo!count();

    double length;
    foo!length();
}

Also note that alias parameters make the names of the actual variables available inside the template:

The general definition is using 'name' of type string.
The int specialization is using 'count'.
The double specialization is using 'length'.
Tuple template parameters

We have seen in the Variable Number of Parameters chapter that variadic functions can take any number and any type of parameters. For example, writeln() can be called with any number of parameters of any type.

Templates can be variadic as well. A template parameter that consists of a name followed by ... allows any number and kind of parameters at that parameter's position. Such parameters appear as a tuple inside the template, which can be used like an AliasSeq.

Let's see an example of this with a template that simply prints information about every template argument that it is instantiated with:

void info(T...)(T args) {
    // ...
}

The template parameter T... makes info a variadic template. Both T and args are tuples:

The following example instantiates that function template with three values of three different types:

import std.stdio;

// ...

void main() {
    info(1, "abc", 2.3);
}

The following implementation simply prints information about the arguments by iterating over them in a foreach loop:

void info(T...)(T args) {
    // 'args' is being used like a tuple:
    foreach (i, arg; args) {
        writefln("%s: %s argument %s",
                 i, typeof(arg).stringof, arg);
    }
}

Note: As seen in the previous chapter, since the arguments are a tuple, the foreach statement above is a compile-time foreach.

The output:

0: int argument 1
1: string argument abc
2: double argument 2.3

Note that instead of obtaining the type of each argument by typeof(arg), we could have used T[i] as well.

We know that template arguments can be deduced for function templates. That's why the compiler deduces the types as int, string, and double in the previous program.

However, it is also possible to specify template parameters explicitly. For example, std.conv.to takes the destination type as an explicit template parameter:

    to!string(42);

When template parameters are explicitly specified, they can be a mixture of value, type, and other kinds. That flexibility makes it necessary to be able to determine whether each template parameter is a type or not, so that the body of the template can be coded accordingly. That is achieved by treating the arguments as an AliasSeq.

Let's see an example of this in a function template that produces struct definitions as source code in text form. Let's have this function return the produced source code as string. This function can first take the name of the struct followed by the types and names of the members specified as pairs:

import std.stdio;

void main() {
    writeln(structDefinition!("Student",
                              string, "name",
                              int, "id",
                              int[], "grades")());
}

That structDefinition instantiation is expected to produce the following string:

struct Student {
    string name;
    int id;
    int[] grades;
}

Note: Functions that produce source code are used with the mixin keyword, which we will see in a later chapter.

The following is an implementation that produces the desired output. Note how the function template makes use of the is expression. Remember that the expression is (arg) produces true when arg is a valid type:

import std.string;

string structDefinition(string name, Members...)() {
    /* Ensure that members are specified as pairs: first the
     * type then the name. */
    static assert((Members.length % 2) == 0,
                  "Members must be specified as pairs.");

    /* The first part of the struct definition. */
    string result = "struct " ~ name ~ "\n{\n";

    foreach (i, arg; Members) {
        static if (i % 2) {
            /* The odd numbered arguments should be the names
             * of members. Instead of dealing with the names
             * here, we use them as Members[i+1] in the 'else'
             * clause below.
             *
             * Let's at least ensure that the member name is
             * specified as a string. */
            static assert(is (typeof(arg) == string),
                          "Member name " ~ arg.stringof ~
                          " is not a string.");

        } else {
            /* In this case 'arg' is the type of the
             * member. Ensure that it is indeed a type. */
            static assert(is (arg),
                          arg.stringof ~ " is not a type.");

            /* Produce the member definition from its type and
             * its name.
             *
             * Note: We could have written 'arg' below instead
             * of Members[i]. */
            result ~= format("    %s %s;\n",
                             Members[i].stringof, Members[i+1]);
        }
    }

    /* The closing bracket of the struct definition. */
    result ~= "}";

    return result;
}

import std.stdio;

void main() {
    writeln(structDefinition!("Student",
                              string, "name",
                              int, "id",
                              int[], "grades")());
}
typeof(this), typeof(super), and typeof(return)

In some cases, the generic nature of templates makes it difficult to know or spell out certain types in the template code. The following three special typeof varieties are useful in such cases. Although they are introduced in this chapter, they work in non-templated code as well.

Template specializations

We have seen template specializations in the Templates chapter. Like type parameters, other kinds of template parameters can be specialized as well. The following is the general definition of a template and its specialization for 0:

void foo(int value)() {
    // ... general definition ...
}

void foo(int value : 0)() {
    // ... special definition for zero ...
}

We will take advantage of template specializations in the meta programming section below.

Meta programming

As they are about code generation, templates are among the higher level features of D. A template is indeed code that generates code. Writing code that generates code is called meta programming.

Due to templates being compile-time features, some operations that are normally executed at runtime can be moved to compile time as template instantiations.

(Note: Compile time function execution (CTFE) is another feature that achieves the same goal. We will see CTFE in a later chapter.)

Executing templates at compile time is commonly based on recursive template instantiations.

To see an example of this, let's first consider a regular function that calculates the sum of numbers from 0 to a specific value. For example, when its argument is 4, this fuction should return the result of 0+1+2+3+4:

int sum(int last) {
    int result = 0;

    foreach (value; 0 .. last + 1) {
        result += value;
    }

    return result;
}

That is an iterative implementation of the function. The same function can be implemented by recursion as well:

int sum(int last) {
    return (last == 0
            ? last
            : last + sum(last - 1));
}

The recursive function returns the sum of the last value and the previous sum. As you can see, the function terminates the recursion by treating the value 0 specially.

Functions are normally run-time features. As usual, sum() can be executed at run time:

    writeln(sum(4));

When the result is needed at compile time, one way of achieving the same calculation is by defining a function template. In this case, the parameter must be a template parameter, not a function parameter:

// WARNING: This code is incorrect.
int sum(int last)() {
    return (last == 0
            ? last
            : last + sum!(last - 1)());
}

That function template instantiates itself by last - 1 and tries to calculate the sum again by recursion. However, that code is incorrect.

As the ternary operator would be compiled to be executed at run time, there is no condition check that terminates the recursion at compile time:

    writeln(sum!4());    // ← compilation ERROR

The compiler detects that the template instances would recurse infinitely and stops at an arbitrary number of recursions:

Error: template instance deneme.sum!(-296) recursive expansion

Considering the difference between the template argument 4 and -296, the compiler restricts template expansion at 300 by default.

In meta programming, recursion is terminated by a template specialization. The following specialization for 0 produces the expected result:

// The general definition
int sum(int last)() {
    return last + sum!(last - 1)();
}

// The special definition for zero
int sum(int last : 0)() {
    return 0;
}

The following is a program that tests sum():

import std.stdio;

void main() {
    writeln(sum!4());
}

Now the program compiles successfully and produces the result of 4+3+2+1+0:

10

An important point to make here is that the function sum!4() is executed entirely at compile time. The compiled code is the equivalent of calling writeln() with literal 10:

    writeln(10);         // the equivalent of writeln(sum!4())

As a result, the compiled code is as fast and simple as can be. Although the value 10 is still calculated as the result of 4+3+2+1+0, the entire calculation happens at compile time.

The previous example demonstrates one of the benefits of meta programming: moving operations from run time to compile time. CTFE obviates some of the idioms of meta programming in D.

Compile-time polymorphism

In object oriented programming (OOP), polymorphism is achieved by inheritance. For example, if a function takes an interface, it accepts objects of any class that inherits that interface.

Let's recall an earlier example from a previous chapter:

import std.stdio;

interface SoundEmitter {
    string emitSound();
}

class Violin : SoundEmitter {
    string emitSound() {
        return "♩♪♪";
    }
}

class Bell : SoundEmitter {
    string emitSound() {
        return "ding";
    }
}

void useSoundEmittingObject(SoundEmitter object) {
    // ... some operations ...
    writeln(object.emitSound());
    // ... more operations ...
}

void main() {
    useSoundEmittingObject(new Violin);
    useSoundEmittingObject(new Bell);
}

useSoundEmittingObject() is benefiting from polymorphism. It takes a SoundEmitter so that it can be used with any type that is derived from that interface.

Since working with any type is inherent to templates, they can be seen as providing a kind of polymorphism as well. Being a compile-time feature, the polymorphism that templates provide is called compile-time polymorphism. Conversely, OOP's polymorphism is called run-time polymorphism.

In reality, neither kind of polymorphism allows being used with any type because the types must satisfy certain requirements.

Run-time polymorphism requires that the type implements a certain interface.

Compile-time polymorphism requires that the type is compatible with how it is used by the template. As long as the code compiles, the template argument can be used with that template. (Note: Optionally, the argument must satisfy template constraints as well. We will see template constraints later below.)

For example, if useSoundEmittingObject() were implemented as a function template instead of a function, it could be used with any type that supported the object.emitSound() call:

void useSoundEmittingObject(T)(T object) {
    // ... some operations ...
    writeln(object.emitSound());
    // ... more operations ...
}

class Car {
    string emitSound() {
        return "honk honk";
    }
}

// ...

    useSoundEmittingObject(new Violin);
    useSoundEmittingObject(new Bell);
    useSoundEmittingObject(new Car);

Note that although Car has no inheritance relationship with any other type, the code compiles successfully, and the emitSound() member function of each type gets called.

Compile-time polymorphism is also known as duck typing, a humorous term, emphasizing behavior over actual type.

Code bloat

The code generated by the compiler is different for every different argument of a type parameter, of a value parameter, etc.

The reason for that can be seen by considering int and double as type template arguments. Each type would have to be processed by different kinds of CPU registers. For that reason, the same template needs to be compiled differently for different template arguments. In other words, the compiler needs to generate different code for each instantiation of a template.

For example, if useSoundEmittingObject() were implemented as a template, it would be compiled as many times as the number of different instantiations of it.

Because it results in larger program size, this effect is called code bloat. Although this is not a problem in most programs, it is an effect of templates that must be known.

Conversely, non-templated version of useSoundEmittingObject() would not have any code repetition. The compiler would compile that function just once and execute the same code for all types of the SoundEmitter interface. In run-time polymorphism, having the same code behave differently for different types is achieved by function pointers on the background. Although function pointers have a small cost at run time, that cost is not significant in most programs.

Since both code bloat and run-time polymorphism have effects on program performance, it cannot be known beforehand whether run-time polymorphism or compile-time polymorphism would be a better approach for a specific program.

Template constraints

The fact that templates can be instantiated with any argument yet not every argument is compatible with every template brings an inconvenience. If a template argument is not compatible with a particular template, the incompatibility is necessarily detected during the compilation of the template code for that argument. As a result, the compilation error points at a line inside the template implementation.

Let's see this by using useSoundEmittingObject() with a type that does not support the object.emitSound() call:

class Cup {
    // ... does not have emitSound() ...
}

// ...

    useSoundEmittingObject(new Cup);   // ← incompatible type

Although arguably the error is with the code that uses the template with an incompatible type, the compilation error points at a line inside the template:

void useSoundEmittingObject(T)(T object) {
    // ... some operations ...
    writeln(object.emitSound());    // ← compilation ERROR
    // ... more operations ...
}

An undesired consequence is that when the template is a part of a third-party library module, the compilation error would appear to be a problem with the library itself.

Note that this issue does not exist for interfaces: A function that takes an interface can only be called with a type that implements that interface. Attempting to call such a function with any other type is a compilation error at the caller.

Template contraints are for disallowing incorrect instantiations of templates. They are defined as logical expressions of an if condition right before the template body:

void foo(T)()
        if (/* ... constraints ... */) {
    // ...
}

A template definition is considered by the compiler only if its constraints evaluate to true for a specific instantiation of the template. Otherwise, the template definition is ignored for that use.

Since templates are a compile-time feature, template constraints must be evaluable at compile time. The is expression that we saw in the is Expression chapter is commonly used in template constraints. We will use the is expression in the following examples as well.

Tuple parameter of single element

Sometimes the single parameter of a template needs to be one of type, value, or alias kinds. That can be achieved by a tuple parameter of length one:

template myTemplate(T...)
        if (T.length == 1) {
    static if (is (T[0])) {
        // The single parameter is a type
        enum bool myTemplate = /* ... */;

    } else {
        // The single parameter is some other kind
        enum bool myTemplate = /* ... */;
    }
}

Some of the templates of the std.traits module take advantage of this idiom. We will see std.traits in a later chapter.

Named constraints

Sometimes the constraints are complex, making it hard to understand the requirements of template parameters. This complexity can be handled by an idiom that effectively gives names to constraints. This idiom combines four features of D: anonymous functions, typeof, the is expression, and eponymous templates.

Let's see this on a function template that has a type parameter. The template uses its function parameter in specific ways:

void use(T)(T object) {
    // ...
    object.prepare();
    // ...
    object.fly(42);
    // ...
    object.land();
    // ...
}

As is obvious from the implementation of the template, the types that this function can work with must support three specific function calls on the object: prepare(), fly(42), and land().

One way of specifying a template constraint for that type is by the is and typeof expressions for each function call inside the template:

void use(T)(T object)
        if (is (typeof(object.prepare())) &&
            is (typeof(object.fly(1))) &&
            is (typeof(object.land()))) {
    // ...
}

I will explain that syntax below. For now, accept the whole construct of is (typeof(object.prepare())) to mean whether the type supports the .prepare() call.

Although such constraints achieve the desired goal, sometimes they are too complex to be readable. Instead, it is possible to give a more descriptive name to the whole constraint:

void use(T)(T object)
        if (canFlyAndLand!T) {
    // ...
}

That constraint is more readable because it is now more clear that the template is designed to work with types that can fly and land.

Such constraints are achieved by an idiom that is implemented similar to the following eponymous template:

template canFlyAndLand(T) {
    enum canFlyAndLand = is (typeof(
    {
        T object;
        object.prepare();  // should be preparable for flight
        object.fly(1);     // should be flyable for a certain distance
        object.land();     // should be landable
    }()));
}

The D features that take part in that idiom and how they interact with each other are explained below:

template canFlyAndLand(T) {
    //        (6)        (5)  (4)
    enum canFlyAndLand = is (typeof(
    { // (1)
        T object;         // (2)
        object.prepare();
        object.fly(1);
        object.land();
 // (3)
    }()));
}
  1. Anonymous function: We have seen anonymous functions in the Function Pointers, Delegates, and Lambdas chapter. The highlighted curly brackets above define an anonymous function.
  2. Function block: The function block uses the type as it is supposed to be used in the actual template. First an object of that type is defined and then that object is used in specific ways. (This code never gets executed; see below.)
  3. Evaluation of the function: The empty parentheses at the end of an anonymous function normally execute that function. However, since that call syntax is within a typeof, it is never executed.
  4. The typeof expression: typeof produces the type of an expression.

    An important fact about typeof is that it never executes the expression. Rather, it produces the type of the expression if that expression would be executed:

        int i = 42;
        typeof(++i) j;    // same as 'int j;'
    
        assert(i == 42);  // ++i has not been executed
    

    As the previous assert proves, the expression ++i has not been executed. typeof has merely produced the type of that expression as int.

    If the expression that typeof receives is not valid, typeof produces no type at all (not even void). So, if the anonymous function inside canFlyAndLand can be compiled successfully for T, typeof produces a valid type. Otherwise, it produces no type at all.

  5. The is expression: We have seen many different uses of the is expression in the is Expression chapter. The is (Type) syntax produces true if Type is valid:
        int i;
        writeln(is (typeof(i)));                  // true
        writeln(is (typeof(nonexistentSymbol)));  // false
    

    Although the second typeof above receives a nonexistent symbol, the compiler does not emit a compilation error. Rather, the effect is that the typeof expression does not produce any type, so the is expression produces false:

    true
    false
    
  6. Eponymous template: As described above, since the canFlyAndLand template contains a definition by the same name, the template instantiation is that definition itself.

In the end, use() gains a more descriptive constraint:

void use(T)(T object)
        if (canFlyAndLand!T) {
    // ...
}

Let's try to use that template with two types, one that satisfies the constraint and one that does not satisfy the constraint:

// A type that does match the template's operations
class ModelAirplane {
    void prepare() {
    }

    void fly(int distance) {
    }

    void land() {
    }
}

// A type that does not match the template's operations
class Pigeon {
    void fly(int distance) {
    }
}

// ...

    use(new ModelAirplane);    // ← compiles
    use(new Pigeon);           // ← compilation ERROR

Named or not, since the template has a constraint, the compilation error points at the line where the template is used rather than where it is implemented.

Using templates in multi-dimensional operator overloading

We have seen in the Operator Overloading chapter that opDollar, opIndex, and opSlice are for element indexing and slicing. When overloaded for single-dimensional collections, these operators have the following responsibilities:

Those operator functions have templated versions as well, which have different responsibilities from the non-templated ones above. Note especially that in multi-dimensional operator overloading opIndex assumes the responsibility of opSlice.

opIndexAssign and opIndexOpAssign have templated versions as well, which operate on a range of elements of the collection.

The user-defined types that define these operators can be used with the multi-dimensional indexing and slicing syntax:

              // Assigns 42 to the elements specified by the
              // indexing and slicing arguments:
              m[a, b..c, $-1, d..e] = 42;
//              ↑   ↑     ↑    ↑
// dimensions:  0   1     2    3

Such expressions are first converted to the ones that call the operator functions. The conversions are performed by replacing the $ characters with calls to opDollar!dimension(), and the index ranges with calls to opSlice!dimension(begin, end). The length and range information that is returned by those calls is in turn used as arguments when calling e.g. opIndexAssign. Accordingly, the expression above is executed as the following equivalent (the dimension values are highlighted):

    // The equivalent of the above:
    m.opIndexAssign(
        42,                    // ← value to assign
        a,                     // ← argument for dimension 0
        m.opSlice!1(b, c),     // ← argument for dimension 1
        m.opDollar!2() - 1,    // ← argument for dimension 2
        m.opSlice!3(d, e));    // ← argument for dimension 3

Consequently, opIndexAssign determines the range of elements from the arguments.

Multi-dimensional operator overloading example

The following Matrix example demonstrates how these operators can be overloaded for a two-dimensional type.

Note that this code can be implemented in more efficient ways. For example, instead of constructing a single-element sub-matrix even when operating on a single element e.g. by m[i, j], it could apply the operation directly on that element.

Additionally, the writeln(__FUNCTION__) expressions inside the functions have nothing to do with the behavior of the code. They merely help expose the functions that get called behind the scenes for different operator usages.

Also note that the correctness of dimension values are enforced by template constraints.

import std.stdio;
import std.format;
import std.string;

/* Works as a two-dimensional int array. */
struct Matrix {
private:

    int[][] rows;

    /* Represents a range of rows or columns. */
    struct Range {
        size_t begin;
        size_t end;
    }

    /* Returns the sub-matrix that is specified by the row and
     * column ranges. */
    Matrix subMatrix(Range rowRange, Range columnRange) {
        writeln(__FUNCTION__);

        int[][] slices;

        foreach (row; rows[rowRange.begin .. rowRange.end]) {
            slices ~= row[columnRange.begin .. columnRange.end];
        }

        return Matrix(slices);
    }

public:

    this(size_t height, size_t width) {
        writeln(__FUNCTION__);

        rows = new int[][](height, width);
    }

    this(int[][] rows) {
        writeln(__FUNCTION__);

        this.rows = rows;
    }

    void toString(void delegate(const(char)[]) sink) const {
        sink.formattedWrite!"%(%(%5s %)\n%)"(rows);
    }

    /* Assigns the specified value to each element of the
     * matrix. */
    Matrix opAssign(int value) {
        writeln(__FUNCTION__);

        foreach (row; rows) {
            row[] = value;
        }

        return this;
    }

    /* Uses each element and a value in a binary operation
     * and assigns the result back to that element. */
    Matrix opOpAssign(string op)(int value) {
        writeln(__FUNCTION__);

        foreach (row; rows) {
            mixin ("row[] " ~ op ~ "= value;");
        }

        return this;
    }

    /* Returns the length of the specified dimension. */
    size_t opDollar(size_t dimension)() const
            if (dimension <= 1) {
        writeln(__FUNCTION__);

        static if (dimension == 0) {
            /* The length of dimension 0 is the length of the
             * 'rows' array. */
            return rows.length;

        } else {
            /* The length of dimension 1 is the lengths of the
             * elements of 'rows'. */
            return rows.length ? rows[0].length : 0;
        }
    }

    /* Returns an object that represents the range from
     * 'begin' to 'end'.
     *
     * Note: Although the 'dimension' template parameter is
     * not used here, that information can be useful for other
     * types. */
    Range opSlice(size_t dimension)(size_t begin, size_t end)
            if (dimension <= 1) {
        writeln(__FUNCTION__);

        return Range(begin, end);
    }

    /* Returns a sub-matrix that is defined by the
     * arguments. */
    Matrix opIndex(A...)(A arguments)
            if (A.length <= 2) {
        writeln(__FUNCTION__);

        /* We start with ranges that represent the entire
         * matrix so that the parameter-less use of opIndex
         * means "all of the elements". */
        Range[2] ranges = [ Range(0, opDollar!0),
                            Range(0, opDollar!1) ];

        foreach (dimension, a; arguments) {
            static if (is (typeof(a) == Range)) {
                /* This dimension is already specified as a
                 * range like 'matrix[begin..end]', which can
                 * be used as is. */
                ranges[dimension] = a;

            } else static if (is (typeof(a) : size_t)) {
                /* This dimension is specified as a single
                 * index value like 'matrix[i]', which we want
                 * to represent as a single-element range. */
                ranges[dimension] = Range(a, a + 1);

            } else {
                /* We don't expect other types. */
                static assert(
                    false, format("Invalid index type: %s",
                                  typeof(a).stringof));
            }
        }

        /* Return the sub-matrix that is specified by
         * 'arguments'. */
        return subMatrix(ranges[0], ranges[1]);
    }

    /* Assigns the specified value to each element of the
     * sub-matrix. */
    Matrix opIndexAssign(A...)(int value, A arguments)
            if (A.length <= 2) {
        writeln(__FUNCTION__);

        Matrix subMatrix = opIndex(arguments);
        return subMatrix = value;
    }

    /* Uses each element of the sub-matrix and a value in a
     * binary operation and assigns the result back to that
     * element. */
    Matrix opIndexOpAssign(string op, A...)(int value,
                                            A arguments)
            if (A.length <= 2) {
        writeln(__FUNCTION__);

        Matrix subMatrix = opIndex(arguments);
        mixin ("return subMatrix " ~ op ~ "= value;");
    }
}

/* Executes the expression that is specified as a string, and
 * prints the result as well as the new state of the
 * matrix. */
void execute(string expression)(Matrix m) {
    writefln("\n--- %s ---", expression);
    mixin ("auto result = " ~ expression ~ ";");
    writefln("result:\n%s", result);
    writefln("m:\n%s", m);
}

void main() {
    enum height = 10;
    enum width = 8;

    auto m = Matrix(height, width);

    int counter = 0;
    foreach (row; 0 .. height) {
        foreach (column; 0 .. width) {
            writefln("Initializing %s of %s",
                     counter + 1, height * width);

            m[row, column] = counter;
            ++counter;
        }
    }

    writeln(m);

    execute!("m[1, 1] = 42")(m);
    execute!("m[0, 1 .. $] = 43")(m);
    execute!("m[0 .. $, 3] = 44")(m);
    execute!("m[$-4 .. $-1, $-4 .. $-1] = 7")(m);

    execute!("m[1, 1] *= 2")(m);
    execute!("m[0, 1 .. $] *= 4")(m);
    execute!("m[0 .. $, 0] *= 10")(m);
    execute!("m[$-4 .. $-2, $-4 .. $-2] -= 666")(m);

    execute!("m[1, 1]")(m);
    execute!("m[2, 0 .. $]")(m);
    execute!("m[0 .. $, 2]")(m);
    execute!("m[0 .. $ / 2, 0 .. $ / 2]")(m);

    execute!("++m[1..3, 1..3]")(m);
    execute!("--m[2..5, 2..5]")(m);

    execute!("m[]")(m);
    execute!("m[] = 20")(m);
    execute!("m[] /= 4")(m);
    execute!("(m[] += 5) /= 10")(m);
}
Summary

The earlier template chapter had the following reminders:

This chapter added the following concepts: