www.digitalmars.com
Last update Wed May 31 17:11:57 2006

Gabaritos

Eu acho q posso dizer com segurança que ninguém entende a mecânica de gabaritos. -- Richard Deyman

Gabaritos são a aproximação de D para programação genérica. Gabaritos são definidos com uma DeclaraçãoGabarito:

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

O corpo da DeclaraçãoGabarito deve ser sintaticamente correto mesmo se nunca instanciado. Análise semântica não é feita até que instanciado. Um gabarito forma seu próprio escopo, e o corpo do gabarito pode conter classes, estruturas, tipos, enums, variáveis, funções e outros gabaritos.

Parâmetros de gabaritos podem ser tipos, valores, ou símbolos. Tipos podem ser qualquer tipo. Parâmetros valor devem ser de um tipo integral, tipo de ponto flutuante ou tipo string e especializações para eles devem resolver para uma constante integral, constante de ponto flutuante, nulo, ou um string literal. Símbolos podem ser qualquer símbolo não-local.

Especializações de parâmetros de gabaritos confinam os valores ou tipos que ParâmetroGabarito pode aceitar.

Padrões de parâmetros de gabaritos são o valor ou tipo para usar para o ParâmetroGabarito caso algum não seja fornecido.

Instanciação Explícita de Gabaritos

Gatbaritos são explicitamente instânciados com:

TemplateInstance:
TemplateIdentifer !( TemplateArgumentList )
TemplateArgumentList:
TemplateArgument
TemplateArgument , TemplateArgumentList
TemplateArgument:
Type
AssignExpression
Symbol

Uma vez instanciado, as declarações dentro do gabarito, chamadas de membros do gabarito, estão no escopo de InstânciaGabarito:

template TFoo(T) { alias T* t; }
...
TFoo!(int).t x; // declara x para ser do tipo

Uma instanciação de gabarito pode ser apelidada:

template TFoo(T) { alias T* t; }
alias TFoo!(int) abc;
abc.t x; // declara x para ser do tipo int*

Múltiplas instanciações de uma DeclaraçãoGabarito com a mesma ListaParâmetroGabarito todas irão se referia à mesma instanciação. Por exemplo:

template TFoo(T) { T f; }
alias TFoo!(int) a;
alias TFoo!(int) b;
...
a.f = 3;
assert(b.f == 3); // a e b se referem à mesma instância de TFoo

Isso é verdade mesmo se InstanciasGabarito são feitas em módulos diferentes.

Se múltiplos gabaritos com o mesmo IdentificadorGabarito são declarados, eles são distintos se eles tem um número de argumentos diferente ou são diferentemente expecializados.

Por exemplo, um simples gabarito de cópia genérica seria:

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

Para usar o gabarito, ele deve primeiro ser instânciado com um tipo especifico:

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

Escopo de Instanciação

InstanciasGabarito são sempre feitas no escopo onde a DeclaraçãoGabarito é declarada, com a adição dos parâmetros do gabarito sendo declarados como apelidos para seus tipos deduzidos.

Por exemplo:



módulo a
template TFoo(T) { void bar() { func(); } }
módulo b
import a;

void func() { }
alias TFoo!(int) f; // erro: func não definida no módulo a

e:



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

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

Especializações de ParâmetroGabarito valores padão são avaliados no escopo da DeclaraçãoGabarito.

Dedução de Argumento

Os tipos de parãmetros de gabaritos são deduzidos para uma instanciação de gabarito particular  coparando o argumento do gabarito com o parâmetro do gabarito orrespondente.

Para cada parâmetro do gabarito, as seguintes regras são aplicadas na ordem até um tipo ser deduzido para cada parâmetro:

  1. Se não há especialização de tipo para o parâmetro, o tipo do parâmetro é ajustado para o argumento do gabarito.
  2. Se a especialização de tipo é dependente de um parâmetro de tipo, o tipo daquele parâmetro é ajustado para ser a parte correspondente do argumento de tipo.
  3. Se após todos os argumentos te tipo serem examinados haver qualquer parâmetro de tipo restando sem nenhum tipo atribuído, eles são atribuídos com tipos correspondentes ai argumento do gabarito correspondente na mesma posição na ListaArgumentoGabarito.
  4. Se aplicar as regras acima não resultou em ecatamente um tipo para cada parâmetro do gabarito, então é um erro.
Por exemplo:
template TFoo(T) { }
alias TFoo!(int) Foo1; // (1) T é deduzido para ser int
alias TFoo!(char*) Foo2; // (1) T é deduzido para ser char*

template TFoo(T : T*) { }
alias TFoo!(char*) Foo3; // (2) T é deduzido para ser char

template TBar(D, U : D[]) { }
alias TBar!(int, int[]) Bar1; // (2) D é deduzido para ser int, U é int[]
alias TBar!(char, int[]) Bar2; // (4) erro, D é char e int

template TBar(D : E*, E) { }
alias TBar!(int*, int) Bar3; // (1) E é int
// (3) D é int*
Quando considerando combinações, uma classe é considerada para ser uma combinação para quaisquer superclasses ou interfaces:
class A { }
class B : A { }

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

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

Parâmetros Valor

Esse exemplo de gabarito foo tem um parâmetro valor que é especializado para 10:
template foo(U : int, int T : 10)
{
U x = T;
}

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

Especialização

Gabaritos podem ser especializados para tipos de argumentos particulares seguindo o identificador de parâmetro do gabarito com um : e o tipo especializado. Por exemplo:
template TFoo(T) { ... } // #1
template TFoo(T : T[]) { ... } // #2
template TFoo(T : char) { ... } // #3
template TFoo(T,U,V) { ... } // #4

alias TFoo!(int) foo1; // intancia #1
alias TFoo!(double[]) foo2; // intancia #2 com T sendo double
alias TFoo!(char) foo3; // intancia #3
alias TFoo!(char, int) fooe; // erro, número de argumentos não combina
alias TFoo!(char, int, int) foo4; // intancia #4
O gabarito escolhido para instanciar é o que é aquele mais especializado que se ajusta aos tipos da ListaArgumentoGabarito. Determinar que é mais especializado é feito do mesmo jeito das regras de ordenação parcial de C++. Se o resultado for ambiguo, é um erro.

Parâmetros Apelido

Parâmetros apelido possibilitam gabaritos serem parametrizados com qualquer tipo de símbolo D, incluindo nomes globais, nomes de tipo, nomes de módulo, nomes de gabarito, e nomes de intância de gabarito. Nomes locais não podem ser usados como parâmetros apelido. É um super-conjunto se usos de parâmetros de gabarito em C++.

Valores Padrão de Parâmetros de Gabaritos

A fuga de parâmetros de gabarito pode ser dada com valores padrão:
template Foo(T, U = int) { ... }
Foo!(uint,long); // instancia Foo com T como uint, e U como long
Foo!(uint); // instancia Foo com T como uint, e U como int

template Foo(T, U = T*) { ... }
Foo!(uint); // instancia Foo com T como uint, e U como uint*

Propriedades Implícitas de Gabaritos

Se um gabarito tem exatamente um membro nele, e o nome desse membro é o mesmo do gabarito, esse membro é assumido para ser referido em uma instanciação de gabarito:
template Foo(T)
{
T Foo; // declara variável Foo do tipo T
}

void test()
{
Foo!(int) = 6; // ao invés de Foo!(int).Foo
}

Gabaritos Classe

ClassTemplateDeclaration:
class Identifier ( TemplateParameterList ) [SuperClass {, InterfaceClass }] ClassBody
Se um gabarito declara exatamente um membro, e esse membro é uma classe com o mesmo nome do gabarito:
template Bar(T)
{
class Bar
{
T member;
}
}
então a semântica equivalente, chamada DeclaraçãoGabaritoClasse podem ser escruta como:
class Bar(T)
{
T member;
}

Gabaritos Função

Se um gabarito declara exatamente um membro, e esse membro é uma função com o mesmo nome do gabarito:
template Square(T)
{
T Square(T t)
{
return t * t;
}
}
então esse gabarito é chamado Gabarito Função. Gabaritos Função podem ser explicitamente instanciados com um !(ListaArgumentoGabarito):
writefln("The square of %s is %s", 3, Square!(int)(3));
ou implicitamente, onde a ListaArgumentoGabarito é deduzida dos tipos dos argumentos da função:
writefln("The square of %s is %s", 3, Square(3)); // T é deduzido para int
Tipos de parâmetros de gabaritos função que são implicitamente deduzidos não podem ter especializações:
template Foo(T : T*)
{
void Foo(T t) { ... }
}

int x,y;
Foo!(int*)(&x); // ok, T não é deduzido de argumento da função
Foo(&y); // erro, T tem especialização
Argumentos de gabarito não deduzidos implicitamente podem ter valores padrão:
template Foo(T, U=T*)
{
void Foo(T t) { U p; ... }
}

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

Gabaritos Recursivos

Características de gabaritos podem ser combinadas para produzir alguns efeitos interessantes, tal como avaliação em tempo de compilação de funções não-triviais. Por exemplo, um gabarito fatorial pode ser escrito:
template factorial(int n : 1)
{
enum { factorial = 1 }
}

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

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

Limitações

Gabaritos não podem ser usados para adicionar membros não-estáticos ou funções à classes. Por exemplo:
class Foo
{
template TBar(T)
{
T xx; // Erro
int func(T) { ... } // Erro

static T yy; // Ok
static int func(T t, int y) { ... } // Ok
}
}
Gabaritos não podem ser declarados dentro de funções.