A Beginner's Guide to D/Templates and Generic Programming/Template Classes
Container classes are usually most useful if they can hold any kind of item. Otherwise, you would have to write a different container for each type you wanted to hold. The traditional object-oriented approach (used by Java before version 1.5) takes advantage of polymorphism. Since all classes derive from Object, a container that holds Object instances can serve as a general-purpose container. This has a number of disadvantages:
- Primitive types (e.g. int) cannot be directly held in the container. You must write a wrapper class (such as Java's Integer) to hold such items.
- The container is not typesafe. You must cast items from Object to their real type when removing them from the container. There are also no particular limits on the actual types of the items in the container, meaning a given container is capable of holding a random mix of types.
- All of this up- and down-casting can have a negative impact on performance.
The simpler solution of just writing a custom container class for each type you want to hold has obvious drawbacks. (Most obviously, it does not promote code reuse.)
Template classes were originally added to C++ to solve this problem, and D's template features are based on those in C++. A library writer first writes a generic container (say, a linked list), as though any type may be contained in it. At the start of the container, the library writer puts a placeholder for the type that will be stored in it. By convention, we usually call this T. This generic class is called a template class, so called because it serves as a template for more concrete implementations.
Syntax
[edit | edit source]A template class in D looks like this:
class Foo(T)
{
}
Note the (T) after the name of the class. This is the template parameter list. To actually use the class, we must instantiate it, by saying something like:
auto f = new Foo!(int);
The template parameter list can have multiple parameters in it, just like a function parameter. A parameter with just a name is taken to be a type. Attempting to pass anything other than a type to T is an error. There are four kinds of template parameters:
- Types
- The T in the example above is a type parameter.
- Constant values
- Nearly any value which can be determined at compile-time can be used as a template parameter. Value template parameters look just like function parameters, e.g. int i in a template parameter list means the template expects an integer literal or a const int.
- Alias parameters
- These parameters can accept essentially any symbol which can be determined at compile-time. This includes functions, other templates, classes, and much more. Alias parameters are declared by putting alias before the parameter's name.
- Tuple parameters
- D supports variadic templates. This is an advanced topic, and is covered later.
Example
[edit | edit source]A simple templated stack (implemented with a linked list) might look like this:
class LinkedStack(T)
{
private Node head;
class Node
{
T item;
Node next;
Node prev;
}
this()
{
head = new Node;
head.prev = head;
head.next = head;
}
void push(T t)
{
Node temp = new Node;
temp.item = t;
head.prev.next = temp;
temp.prev = head.prev;
temp.next = head;
head.prev = temp;
}
T pop()
{
Node temp = head.prev;
temp.prev.next = head;
head.prev = temp.prev;
return temp.item;
}
T peek()
{
return head.prev.item;
}
}
To use the above example, we might say:
void main() {
auto list = new LinkedStack!(int);
list.push(1);
list.push(2);
list.push(3);
auto strList = new LinkedStack!(string);
strList.push("hello");
strList.push("world!");
}
Note the syntax: LinkedStack!(int). This is a template instantiation. This tells the compiler to generate a version of the LinkedStack class where T is replaced with int. The type of the list variable in the above example is LinkedStack!(int). The type of strList is LinkedStack!(string). These two types are distinct from each other. In terms of inheritance, they are only related insofar as they both inherit from Object.