www.digitalmars.com
Last update Mon Jun 19 18:30:22 2006

Templates

I think that I can safely say that nobody understands template mechanics. -- Richard Deyman

Templates are D's approach to generic programming. Templates are defined with a TemplateDeclaration:

TemplateDeclaration:
	template TemplateIdentifier ( TemplateParameterList )
		{ DeclDefs }

TemplateIdentifier:
	Identifier

TemplateParameterList
	TemplateParameter
	TemplateParameter , TemplateParameterList

TemplateParameter:
	TypeParameter
	ValueParameter
	AliasParameter

TemplateTypeParameter:
	Identifier
	Identifier TemplateTypeParameterSpecialization
	Identifier TemplateTypeParameterDefault
	Identifier TemplateTypeParameterSpecialization TemplateTypeParameterDefault

TemplateTypeParameterSpecialization:
	 : Type

TemplateTypeParameterDefault:
	 = Type

TemplateValueParameter:
	Declaration
	Declaration TemplateValueParameterSpecialization
	Declaration TemplateValueParameterDefault
	Declaration TemplateValueParameterSpecialization TemplateValueParameterDefault

TemplateValueParameterSpecialization:
	 : ConditionalExpression

TemplateValueParameterDefault:
	 = ConditionalExpression

TemplateAliasParameter:
	alias Identifier
	alias Identifier TemplateAliasParameterSpecialization
	alias Identifier TemplateAliasParameterDefault
	alias Identifier TemplateAliasParameterSpecialization TemplateAliasParameterDefault

TemplateAliasParameterSpecialization:
	 : Type

TemplateAliasParameterDefault:
	 = Type

The body of the TemplateDeclaration must be syntactically correct even if never instantiated. Semantic analysis is not done until instantiated. A template forms its own scope, and the template body can contain classes, structs, types, enums, variables, functions, and other templates.

Template parameters can be types, values, or symbols. Types can be any type. Value parameters must be of an integral type, floating point type, or string type and specializations for them must resolve to an integral constant, floating point constant, null, or a string literal. Symbols can be any non-local symbol.

Template parameter specializations constrain the values or types the TemplateParameter can accept.

Template parameter defaults are the value or type to use for the TemplateParameter in case one is not supplied.

Explicit Template Instantiation

Templates are explicitly instantiated with:

TemplateInstance:
	TemplateIdentifer !( TemplateArgumentList )

TemplateArgumentList:
	TemplateArgument
	TemplateArgument , TemplateArgumentList

TemplateArgument:
	Type
	AssignExpression
	Symbol

Once instantiated, the declarations inside the template, called the template members, are in the scope of the TemplateInstance:

template TFoo(T) { alias T* t; }
...
TFoo!(int).t x;	// declare x to be of type int*

A template instantiation can be aliased:

template TFoo(T) { alias T* t; }
alias TFoo!(int) abc;
abc.t x;	// declare x to be of type int*

Multiple instantiations of a TemplateDeclaration with the same TemplateParameterList all will refer to the same instantiation. For example:

template TFoo(T) { T f; }
alias TFoo!(int) a;
alias TFoo!(int) b;
...
a.f = 3;
assert(b.f == 3);	// a and b refer to the same instance of TFoo

This is true even if the TemplateInstances are done in different modules.

If multiple templates with the same TemplateIdentifier are declared, they are distinct if they have a different number of arguments or are differently specialized.

For example, a simple generic copy template would be:

template TCopy(T)
{
    void copy(out T to, T from)
    {
	to = from;
    }
}

To use the template, it must first be instantiated with a specific type:

int i;
TCopy!(int).copy(i, 3);

Instantiation Scope

TemplateInstantances are always performed in the scope of where the TemplateDeclaration is declared, with the addition of the template parameters being declared as aliases for their deduced types.

For example:



module a
template TFoo(T) { void bar() { func(); } }
module b
import a;

void func() { }
alias TFoo!(int) f;	// error: func not defined in module a

and:



module a
template TFoo(T) { void bar() { func(1); } }
void func(double d) { }
module b
import a;

void func(int i) { }
alias TFoo!(int) f;
...
f.bar();	// will call a.func(double)

TemplateParameter specializations and default values are evaluated in the scope of the TemplateDeclaration.

Argument Deduction

The types of template parameters are deduced for a particular template instantiation by comparing the template argument with the corresponding template parameter.

For each template parameter, the following rules are applied in order until a type is deduced for each parameter:

  1. If there is no type specialization for the parameter, the type of the parameter is set to the template argument.
  2. If the type specialization is dependent on a type parameter, the type of that parameter is set to be the corresponding part of the type argument.
  3. If after all the type arguments are examined there are any type parameters left with no type assigned, they are assigned types corresponding to the template argument in the same position in the TemplateArgumentList.
  4. If applying the above rules does not result in exactly one type for each template parameter, then it is an error.
For example:
template TFoo(T) { }
alias TFoo!(int) Foo1;		// (1) T is deduced to be int
alias TFoo!(char*) Foo2;	// (1) T is deduced to be char*

template TFoo(T : T*) { }
alias TFoo!(char*) Foo3;	// (2) T is deduced to be char

template TBar(D, U : D[]) { }
alias TBar!(int, int[]) Bar1;	// (2) D is deduced to be int, U is int[]
alias TBar!(char, int[]) Bar2;	// (4) error, D is both char and int

template TBar(D : E*, E) { }
alias TBar!(int*, int) Bar3;	// (1) E is int
				// (3) D is int*
When considering matches, a class is considered to be a match for any super classes or interfaces:
class A { }
class B : A { }

template TFoo(T : A) { }
alias TFoo!(B) Foo4;		// (3) T is B

template TBar(T : U*, U : A) { }
alias TBar!(B*, B) Foo5;	// (2) T is B*
				// (3) U is B

Value Parameters

This example of template foo has a value parameter that is specialized for 10:
template foo(U : int, int T : 10)
{
    U x = T;
}

void main()
{
    assert(foo!(int, 10).x == 10);
}

Specialization

Templates may be specialized for particular types of arguments by following the template parameter identifier with a : and the specialized type. For example:
template TFoo(T)        { ... } // #1
template TFoo(T : T[])  { ... } // #2
template TFoo(T : char) { ... } // #3
template TFoo(T,U,V)    { ... } // #4

alias TFoo!(int) foo1;	       // instantiates #1
alias TFoo!(double[]) foo2;    // instantiates #2 with T being double
alias TFoo!(char) foo3;        // instantiates #3
alias TFoo!(char, int) fooe;   // error, number of arguments mismatch
alias TFoo!(char, int, int) foo4; // instantiates #4
The template picked to instantiate is the one that is most specialized that fits the types of the TemplateArgumentList. Determine which is more specialized is done the same way as the C++ partial ordering rules. If the result is ambiguous, it is an error.

Alias Parameters

Alias parameters enable templates to be parameterized with any type of D symbol, including global names, type names, module names, template names, and template instance names. Local names may not be used as alias parameters. It is a superset of the uses of template template parameters in C++.

Template Parameter Default Values

Trailing template parameters can be given default values:
template Foo(T, U = int) { ... }
Foo!(uint,long); // instantiate Foo with T as uint, and U as long
Foo!(uint);	 // instantiate Foo with T as uint, and U as int

template Foo(T, U = T*) { ... }
Foo!(uint);	 // instantiate Foo with T as uint, and U as uint*

Implicit Template Properties

If a template has exactly one member in it, and the name of that member is the same as the template name, that member is assumed to be referred to in a template instantiation:
template Foo(T)
{
    T Foo;	// declare variable Foo of type T
}

void test()
{
    Foo!(int) = 6;	// instead of Foo!(int).Foo
}

Class Templates

ClassTemplateDeclaration:
    class Identifier ( TemplateParameterList ) [SuperClass {, InterfaceClass }] ClassBody
If a template declares exactly one member, and that member is a class with the same name as the template:
template Bar(T)
{
    class Bar
    {
	T member;
    }
}
then the semantic equivalent, called a ClassTemplateDeclaration can be written as:
class Bar(T)
{
    T member;
}

Function Templates

If a template declares exactly one member, and that member is a function with the same name as the template:
FunctionTemplateDeclaration:
    Type Identifier ( TemplateParameterList ) ( FunctionParameterList ) FunctionBody

A function template to compute the square of type T is:

T Square(T)(T t)
{
    return t * t;
}
Function templates can be explicitly instantiated with a !(TemplateArgumentList):
writefln("The square of %s is %s", 3, Square!(int)(3));
or implicitly, where the TemplateArgumentList is deduced from the types of the function arguments:
writefln("The square of %s is %s", 3, Square(3));  // T is deduced to be int
Function template type parameters that are to be implicitly deduced may not have specializations:
void Foo(T : T*)(T t) { ... }

int x,y;
Foo!(int*)(&x);  // ok, T is not deduced from function argument
Foo(&y);         // error, T has specialization
Template arguments not implicitly deduced can have default values:
void Foo(T, U=T*)(T t) { U p; ... }

int x;
Foo(&x);    // T is int, U is int*

Recursive Templates

Template features can be combined to produce some interesting effects, such as compile time evaluation of non-trivial functions. For example, a factorial template can be written:
template factorial(int n : 1)
{
    enum { factorial = 1 }
}

template factorial(int n)
{
    enum { factorial = n* factorial!(n-1) }
}

void test()
{
    writefln("%s", factorial!(4));	// prints 24
}

Limitations

Templates cannot be used to add non-static members or functions to classes. For example:
class Foo
{
    template TBar(T)
    {
	T xx;			// Error
	int func(T) { ... }	// Error

	static T yy;				// Ok
	static int func(T t, int y) { ... } 	// Ok
    }
}
Templates cannot be declared inside functions.