Objects in a Templated World

October 2012

Templates and Objects really don't get along. They can live in the same house and order a pizza for dinner together; they make it a point to survive. While I won't be covering anything new, this is a look at the issues in the context of the D programming language. A brief look at this strife.

The Strife

Glad to finally be done with that list.

Virtual Functions

To support polymorphic behavior of objects, functions are placed in a vtable. The table provides a simple and mostly transparent way to swap out method calls without disrupting where to look for the method.

Templates can't be placed in this setup because they don't exist unless they are used, and so if they aren't used there is nothing to put in the vtable to allow other objects to override them. To create the appropriate vtable elements upon use requires not only the act of using that function, but also creating the same vtable for all inheriting classes both inside the program and outside.

Typed Rabbits

Template bloat is similarly discussed, in the case of a templated type we have type bloat. A class/struct instantiated with one set of type parameters will be different from one with a different selection of types. This shouldn't be a surprise from the way I have described it, but it can feel as though there is something wrong here.


class Superhero(T) {
    ArchVillainType!T getEnemy();
}

It could easily be seen here that Superhero!Superman would not be the same type as Superhero!Batman. Their enemies just aren't the same. However there is still that desire that they just might be the same.


Superhero my = new Superhero!SpiderMan();

Why can't my superhero be stored!!!! He implements the getEnemy just like every other Superhero. We know why, the Joker is not the same as the Green Goblin. This makes sense but that doesn't mean it is always obvious when being tripped up that it comes back to the templated type.

Other Languages

Languages which employ Generics have similar issues. A common solution is a type erasure, where the specific type is erased and is thus passable as its non-generic structure. To maintain polymorphism and type safety some additional code may also be inserted[1]. In Java the first issue is avoid since Generics do not provide function definitions that don't exist without use. C# provides generic method overriding by keeping some extra lookup tables combined with a JIT to build out code when needed[2].

Go seems to hate these issues so much that it doesn't include templates, generics or even inheritance. It has successfully avoid these issues.

Storing Ranges

The most common place to find yourself asking, "why not?" in D is when using Ranges. Ranges provide a means to iterate over data, they also tend to be very template heavy.


import std.algorithm;
import std.range;
import std.random;

void main() {
    auto constant = 3;
    auto data = iota(9).
        map!(a => uniform(3,8)).
        take(5).
        map!(a => a + constant);
    pragma(msg, typeof(data));
}

The function above does a couple transformations on some random numbers between [3, 8]. And the compiler happily informs us the type of data is:

MapResult!(__lambda4,MapResult!(__lambda2,Result))

This is an undeclarable type. Or as coined by Andrei a Voldemort Type[3]. Now what if we want to change what data is referencing.


    data = iota(9).
        map!(a => a + constant);

This would present us with an error:

Error: cannot implicitly convert expression (map(iota(9))) of type
MapResult!(__lambda6,Result) to
MapResult!(__lambda4,MapResult!(__lambda2,Result))

Well that isn't very nice. And without an understanding of how types are named it is pretty scary. To add to the confusion of how ranges are stored, some operations allow for the original range type to be returned.


import std.algorithm;
import std.range;

void main() {
    auto arr = [4,5,7,9,0,8];
    auto data = arr.take(5);
    data = arr.find(5);
    pragma(msg, typeof(data)); // int[]
}

Ranges come in many forms and can have varying semantics. The different types of ranges as defined in std.range[4] have a tendency to build on each other and allow for a description of abilities which can be required by many algorithms.

Some extra food for thought: A class could be used to build out the Range primitives. In this case the Range will have reference semantics. Generally Ranges are created using struct which results in value semantics being dominating, not that all struct based Ranges will have value semantics.

Using Templates with Inheritance

Storing templated types isn't that hard as D has eliminated the need to specify the stored type locally. When dealing with objects and polymorphism things become a little more work.

Consider a desire to store addable types in a collection. In D operator overloading for such is handled via templates. Need some code to further this discussion so how about a nice Addable type.


class Addable(T) {
    T value;

    this(T start) {
        value = start;
    }

    Addable!T add(Addable!T rhs) {
        return new Addable!T(value + rhs.value);
    }
}

Perfect. All addable types provide an add method taking their Addable. It is templated allowing for some forwarding of operations. However we can't get an Addable collection from this.

Addable[] foo

This is not valid, there is no type Addable, remember we declared a template and templates create new types. Addable!int is different from Addable!double so on and so forth. On top of that we still have been unable to use the operator overloading. Instead we layer on some indirection and it should become blatantly clear what has been wrong with the desire to inherit and operate on desperate types.


class MyAddable(T) : Addable {
    T value;

    this(T start) {
        value = start;
    }

    override MyAddable add(Addable rhs) {
        auto mrhs = cast(MyAddable!T) rhs;
        if(mrhs)
            return new MyAddable(value + mrhs.value);

        return new MyAddable(0);
    }
}

interface Addable {
    Addable add(Addable rhs);

    Addable opBinary(string op)(Addable rhs) if(op == "+") {
        return add(rhs);
    }
}

Addable becomes an interface, abstract class, or class which provides a virtual function add and a template for handling the addition operator. A new type is introduced to handle the actual operation by overriding the virtual functions called by the template. The MyAddable.add method demonstrates just how under specified my Addable interface is. It may provide an add method, but it is incapable of addition when receiving a type not equal. That is a fault of the object-oriented design than the fault of templates or inheritance. At least it fulfils the requirements so desired.


import std.algorithm;
import std.range;

void main() {
    Addable[] a = [new MyAddable!int(5), new MyAddable!int(7)];
    auto b = reduce!"a + b"(a);
}

Be warned that a MyAddable[] is unusable with reduce since the templated addition is only returning Addable and the reduce function is expecting to be reducing MyAddables.

Requesting a Range for Function Signature

Getting back to the thick of things it is time to demonstrate a more legitimate use to the above technique. Ranges are just so awesome that sometimes an interface will want to require the returning of a Range for a given type.

This is actually really simple to do because all the hard work has been done in std.range. Each range type has a specified interface that must be met to classify as a Range of that type. It gets better, when using all the fancy std.algorithms to filter and change the desired elements of iteration there is a simple one function call to turn it in to an abiding Range.

inputRangeObject(myFancyFilteredRange);

This will handle creating a Range type that meets the most functionality provided by the range myFancyFilteredRange. So don't bother looking for forwardRangeObject or randomAccessRangeObject, they just aren't needed.

  1. http://docs.oracle.com/javase/tutorial/java/generics/erasure.html
  2. http://stackoverflow.com/questions/6573557/clr-how-virtual-generic-method-call-is-implemented
  3. http://www.drdobbs.com/article/print?articleId=232901591&siteSectionName=cpp
  4. http://dlang.org/phobos/std_range.html