A Digitalmars D programozási nyelv

Sablonok

D-ben a generikus programozást a template-ek segítik. Egy template-et egy TemplateDeclaration-nel definiálhatunk.

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 TemplateAliasParameterDefault

TemplateAliasParameterDefault:
= Type

A templatedeklaráció szintaktikusan helyes kell legyen, még akkor is, ha az illető template-et sohasem példányosítják A szemantikai elemzés csak példányosításkor történik meg. Egy template-nek saját scope-ja van, amely tartalmazhat osztályokat, struct-okat, típusokat, felsorolási típusokat, változókat, függvényeket, ill. más template-eket. Template paraméter lehet típus, érték, vagy szimbólum. A típusargumentum tetszőleges típus lehet. Az értékargumentumok csak beépített típusúak lehetnek, és csak egész értékre specializálhatunk. Szimbólum bármilyen nem-lokális szimbólum.

Lehetőség van a template-ek paraméterek szerinti specializálására, illetve default argumentum érték megadására.

Példányositás

A template példányositás a következő:

TemplateInstance:
TemplateIdentifer !( TemplateArgumentList )

TemplateArgumentList:
TemplateArgument
TemplateArgument , TemplateArgumentList

TemplateArgument:
Type
AssignExpression
Symbol

A példányosítás után a template-ben található deklarációk (ún. template memberek) a létrejött példány scope-jában vannak.

template TFoo(T) { alias T* t; }
... TFoo!(int).t x; // x int* tipusú lesz

A létrejött példánynak álnév adható.

template TFoo(T) { alias T* t; }
alias TFoo!(int) abc;
abc.t x; // x int* típusú lesz

Adott template deklaráció többszöri példányosítása ugyanazzal a paraméterlistával mindig ugyanarra a példányra fog hivatkozni.

template TFoo(T) { T f; }
alias TFoo(int) a;
alias TFoo(int) b;
... a.f = 3;
assert(b.f == 3); // a és b ugyanaz a példány

Ez akkor is igaz, ha a példányosítások különböző modulokban történnek. Ha azonos néven, de különböző hosszú paraméterlistával, vagy más paraméter specializációval deklarálunk template-eket, akkor azok különbözőek. Pl egy általános másoló template a következő:

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

Hogy egy template-et használni tudjunk, előbb példányosításra van szükség.

int i;
TCopy!(int).copy(i, 3);
A példányosítás hatóköre

A példányosítás mindig abban a scope-ban történik, melyben a template-et deklarálták. A template paraméterek álnévként szolgálnak az argumentumdedukció eredményeként kapott típusra.

Pl:

-------- module a ---------
template TFoo(T) { void bar() { func(); } }
-------- module b ---------
import a;
void func() { }
alias TFoo!(int) f; // hiba: func nem definiálták az “a” module-ban

Pl 2:

-------- 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(); // a.func(double) fog hívódni

A paraméter specializációk és default értékek a deklaráció scope-jában értékelődnek ki.

Argumentum dedukció

A template paraméterek típusának kikövetkeztetése egy adott példányosítás során (=dedukció) úgy történik, hogy az argumentumot a megfelelő helyen levő paraméterrel hasonlítjuk össze.

template TFoo(T) { }
alias TFoo!(int) Foo1; // (1) T int lesz
alias TFoo!(char*) Foo2; // (1) T char* lesz
template TFoo(T : T*) { }
alias TFoo!(char*) Foo3; // (2) T char lesz
template TBar(D, U : D[]) { }
alias TBar!(int, int[]) Bar1; // (2) D int, U int[] lesz
alias TBar!(char, int[]) Bar2; // (4) hiba, D char és int is
template TBar(D : E*, E) { }
alias TBar!(int*, int) Bar3; // (1) E int
// (3) D int*
Értékek mint argumentumok

Az alábbi példában foo “érték típusú” paramétere, mely most 10-re van specializálva.

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

Specializálás

A template-ek specializálhatók különféle argumentumokra oly módon, hogy a paraméter neve mögé egy kettőspontot teszünk, majd leírjuk a kívánt típust, amire specializálni szeretnénk.

template TFoo(T) { ... } // #1
template TFoo(T : T[]) { ... } // #2
template TFoo(T : char) { ... } // #3
template TFoo(T,U,V) { ... } // #4
alias TFoo!(int) foo1; // #1 példányosul
alias TFoo!(double[]) foo2; // #2 példányosul, T pedig double lesz
alias TFoo!(char) foo3; // #3 példányosul
alias TFoo!(char, int) fooe; // hiba, egyik template-tel sem egyezik meg az argumentumok száma
alias TFoo!(char, int, int) foo4; // #4 példányosul

Mindig a legspecializáltabb template példányosul, ezt a C++ döntési szabályaihoz hasonlóan dől el. Ha több template is megfelel, az hibát jelent.

Default paraméterértékek

Egy template paraméternek lehet default értéke:

template Foo(T, U = int) { ... }
Foo!(uint,long); // T uint, U long lesz
Foo!(uint); // T uint, U int lesz
template Foo(T, U = T*) { ... }
Foo!(uint); // T uint, U uint* lesz

Implicit Template

Ha egy template-nek pontosan egy tagja van, és annak neve megegyezik a template-ével, akkor egy adott példányosításkor feltételezzük, hogy arra történik hivatkozás:

template Foo(T)
{
T Foo; // a Foo változó T tipusú
}
void test()
{
Foo!(int) = 6; // irható Foo!(int).Foo helyett
}

Class Template-ek

ClassTemplateDeclaration:
class Identifier ( TemplateParameterList ) [SuperClass {, InterfaceClass }] ClassBody

Ha egy template-nek pontosan egy tagja van, mely egy class, és melynek neve megegyezik a template-ével:

template Bar(T)
{
class Bar
{
T member;
}
}

akkor ez írható a következő formában:

class Bar(T)
{
T member;
}

Rekurzió Template-ekkel

A template-ek tulajdonságait jól kihasználva érdekes hatást érhetünk el, mint például amilyen egy nem-triviális függvény fordítási idejű kiértékelése, Pl egy faktoriális template a következő lehet:

template factorial(int n : 1)
{
enum { factorial = 1 }
}
template factorial(int n)
{
// a . használata jelenti, hogy a globális template-et használjuk
enum { factorial = n* .factorial!(n-1) }
}
void test()
{
printf("%d\n", factorial!(4)); // 24 kiíratása
}

Korlátok

Egy template nem használható arra, hogy nem-statikus tagokat vegyünk fel egy osztályba vagy függvénybe. Pl.

class Foo
{
template TBar(T)
{
T xx; // Hiba
int func(T) { ... } // Hiba
static T yy; // Ok
static int func(T t, int y) { ... } // Ok
}
}

Ezen kívül egy template nem deklarálható függvényeken belül.

Template metaprogramozás

A D különböző nyelvi elemekkel támogatja a template metaprogramozást. Segítségükkel nem csak hatékonyab metaprogramokat írhatunk, de nagyságrendekkel olvashatóbb kódot is kapunk. D-ben két szerkezetet is kapunk arra, hogy bizonyos kifejezéseket fordítási időben értékeljünk ki. Ezek pedig az is és a static_if. Az is(typeof(expr)) kifejezést igazat ad vissza, ha az expr egy érvényes kifejezés, ellenkező esetben pedig hamist, anélkül, hogy a fordítás leállna. A static_if szerkezet pedig egy logikai kifejezést értekel ki fordítási időben, ha ez megtehető. Csupán ez a két funkció megfelezi a template metaprogramok komplexitását.

A static_if kifejezés használata nélkül a faktoriális metaprogram a következőképpen néz ki D nyelven:

template factorial(int n) { const factorial = n * factorial!(n-1); } template factorial(int n : 1) { const factorial = 1; }

Ezzel szemben static_if használatával a kód meglepően hasonlít a futási idejű változathoz:

template factorial(int n) { static if (n == 1) const factorial = 1; else const factorial = n * factorial!(n-1); }

Érdemes megemlíteni még az std.traits könyvtárat, amely arra hivatott, hogy kifejezésekből és szimbólumokból fordítási időben vonhassunk ki értékes információkat. Ilyen lehet például, hogy egy template paraméter szám-e, pointer-e, használható-e függvényként, és még hosszú a sor.