D Programming Language 2.0

- Comments
- D
- Search
- Downloads
- Home

Last update Wed Oct 17 15:41:33 2007

# 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:
    	TemplateTypeParameter
    	TemplateValueParameter
    	TemplateAliasParameter
    	TemplateTupleParameter

    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

    TemplateTupleParameter:
    	Identifier ...

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, symbols, or tuples. 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. Tuples are a sequence of 0 or more types, values or symbols.

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 TemplateArgumentList, before implicit conversions, 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.

Even if template arguments are implicitly converted to the same template parameter type, they still refer to different instances:

    struct TFoo(int x) { }
    static assert(is(TFoo!(3) == TFoo!(2 + 1)));   // 3 and 2+1 are both 3 of type int
    static assert(!is(TFoo!(3) == TFoo!(3u)));     // 3u and 3 are different types

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 TBar(T : T*) { }
    alias TBar!(char*) Foo3;	// (2) T is deduced to be char

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

    template TDef(D : E*, E) { }
    alias TDef!(int*, int) Bar3;	// (1) E is int
    				// (3) D is int*

Deduction from a specialization can provide values for more than one parameter:

    template Foo(T: T[U], U)
    {
        ...
    }

    Foo!(int[long])  // instantiates Foo with T set to int, U set to long

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, local names, typedef names, module names, template names, and template instance names. It is a superset of the uses of template template parameters in C++.

- Global names

      int x;

      template Foo(alias X)
      {
          static int* p = &X;
      }

      void test()
      {
          alias Foo!(x) bar;
          *bar.p = 3;		// set x to 3
          static int y;
          alias Foo!(y) abc;
          *abc.p = 3;		// set y to 3
      }

- Type names

      class Foo
      {
          static int p;
      }

      template Bar(alias T)
      {
          alias T.p q;
      }

      void test()
      {
          alias Bar!(Foo) bar;
          bar.q = 3;	// sets Foo.p to 3
      }

- Module names

      import std.string;

      template Foo(alias X)
      {
      	alias X.toString y;
      }

      void test()
      {
          alias Foo!(std.string) bar;
          bar.y(3);	// calls std.string.toString(3)
      }

- Template names

      int x;

      template Foo(alias X)
      {
          static int* p = &X;
      }

      template Bar(alias T)
      {
          alias T!(x) abc;
      }

      void test()
      {
          alias Bar!(Foo) bar;
          *bar.abc.p = 3;	// sets x to 3
      }

- Template alias names

      int x;

      template Foo(alias X)
      {
          static int* p = &X;
      }

      template Bar(alias T)
      {
          alias T.p q;
      }

      void test()
      {
          alias Foo!(x) foo;
          alias Bar!(foo) bar;
          *bar.q = 3;		// sets x to 3
      }

## 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
    }

## Tuple Parameters

If the last template parameter in the TemplateParameterList is declared as a TemplateTupleParameter, it is a match with any trailing template arguments. The sequence of arguments form a Tuple. A Tuple is not a type, an expression, or a symbol. It is a sequence of any mix of types, expressions or symbols.

A Tuple whose elements consist entirely of types is called a TypeTuple. A Tuple whose elements consist entirely of expressions is called an ExpressionTuple.

A Tuple can be used as an argument list to instantiate another template, or as the list of parameters for a function.

    template Print(A ...)
    {
        void print()
        {
    	writefln("args are ", A);
        }
    }

    template Write(A ...)
    {
        void write(A a)	// A is a TypeTuple
    			// a is an ExpressionTuple
        {
    	writefln("args are ", a);
        }
    }

    void main()
    {
        Print!(1,'a',6.8).print();			  // prints: args are 1a6.8
        Write!(int, char, double).write(1, 'a', 6.8); // prints: args are 1a6.8
    }

Template tuples can be deduced from the types of the trailing parameters of an implicitly instantiated function template:

    template Foo(T, R...)
    {
        void Foo(T t, R r)
        {
    	writefln(t);
    	static if (r.length)	// if more arguments
    	    Foo(r);		// do the rest of the arguments
        }
    }

    void main()
    {
        Foo(1, 'a', 6.8);
    }

prints:

    1
    a
    6.8

The tuple can also be deduced from the type of a delegate or function parameter list passed as a function argument:

    import std.stdio;

    /* R is return type
     * A is first argument type
     * U is TypeTuple of rest of argument types
     */
    R delegate(U) Curry(R, A, U...)(R delegate(A, U) dg, A arg)
    {
        struct Foo
        {
    	typeof(dg) dg_m;
    	typeof(arg) arg_m;

    	R bar(U u)
    	{
    	    return dg_m(arg_m, u);
    	}
        }

        Foo* f = new Foo;
        f.dg_m = dg;
        f.arg_m = arg;
        return &f.bar;
    }

    void main()
    {
        int plus(int x, int y, int z)
        {
    	return x + y + z;
        }

        auto plus_two = Curry(&plus, 2);
        writefln("%d", plus_two(6, 8));	// prints 16
    }

The number of elements in a Tuple can be retrieved with the .length property. The nth element can be retrieved by indexing the Tuple with [n], and sub tuples can be created with the slicing syntax.

Tuples are static compile time entities, there is no way to dynamically change, add, or remove elements.

If both a template with a tuple parameter and a template without a tuple parameter exactly match a template instantiation, the template without a TemplateTupleParameter is selected.

## 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.

Copyright © 1999-2007 by Digital Mars, All Rights Reserved | Page generated by Ddoc. | Comments