logo
Contato | Sobre...        
rebarba rebarba

Rodrigo Strauss :: Blog

follow us in feedly

Tutorial de STL, parte 3/4: Ainda templates

Como prometido, nessa parte veremos como resolver o nosso problema de somar um número complexo e manter nosso código genérico.

Como dito na parte 1/2, temos basicamente duas soluções para esse problema. A primeira chama-se especialização de templates e é utilizada justamente em situações onde o template não consegue resolver o problema para todos os tipos de dados existentes. No nosso caso, o template de soma não é compatível com números complexos, já que precisamos somar a parte real e a parte imaginária separadamente. Para resolver esse problema criaremos uma especialização do template soma que contemple nosso complex_number:

template <typename T>
T soma(T x, T y)
{
  return x + y;
}

struct complex_number
{
  double a;
  double b;
};
 
template <>
complex_number soma<complex_number>(complex_number x, complex_number y)
{
  complex_number c;
  c.a = x.a + y.a;
  c.b = x.b + y.b;
  return c;
}
int _tmain(int argc, _TCHAR* argv[])
{
  complex_number c1  = {10, 20}, c2  = {40, 30};
  soma<complex_number>(c1, c2);
 
  return 0;
}

Nesse caso especializamos o template para tratar as particularidades do tipo complex_number. Note que a definição da especialização do template vem depois da definição da classe complex_number. Isso nos faz lembrar que essa especialização poderia ser fornecida juntamente com o header do complex_number, e não junto com o header do template soma. Dessa forma, quando você criar um tipo, você pode também fornecer junto com ele as especializações necessárias para que templates conhecidos usem o seu tipo.

Outra solução - melhor na minha opinião - é fazer com que o nosso tipo complex_number suporte o operador de soma, para que ele possa ser somado como qualquer outro número. Essa solução é melhor por tentar equalizar o tipo complex_number com os demais tipos, fazendo com que ele possa ser usado por mais funções template sem precisar especializar todas elas. Nosso código ficaria dessa forma:

template <typename T>
T soma(T x, T y)
{
  return x + y;
}
 
struct complex_number
{
  double a;
  double b;
  complex_number operator+(const complex_number& c)
  {
    complex_number result;

    result.a = a + c.a;
    result.b = b + c.b;
 
    return result;
  }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
  complex_number c1  = {10, 20}, c2  = {40, 30}, c3;

  c3 = soma(c1, c2);
 
  ASSERT(c3.a == 50 && c3.b == 50);
 
  return 0;
}

Nesse caso o template de soma continua exatamente o mesmo, o tipo deve suportar ser usado pelo template, e não o contrário. É importante saber esse conceito, pois ele é bastante usado pela STL. Muitos templates da STL esperam que o tipo usando suporte determinados operadores ou que tenham determinados typedefs para funcionar. Isso é o que se chama de polimosfismo em tempo de compilação: ao invés deu usar interfaces (classes abstratas) para garantir a interface (forma como um tipo se apresenta) dos objetos, isso tudo é feito somente tendo os nomes iguais. Como os templates são resolvidos em tempo de compilação, essa verificação é segura e garante que tudo funcionará perfeitamente em tempo de execução. Isso também é uma limitação, já que a resolução só pode ser feita em tempo de compilação - ao contrário do dynamic_cast.

Especialização de templates é um tópico interessante, já que é o recurso que possibilita a técnica arcana de metaprogramação, que nos permite fazer trechos dos programa que rodam em tempo de compilação. O exemplo clássico de metaprogramação usando templates é o cálculo do fatorial em tempo de compilação. Primeiro veja a implementação comum de fatorial, que é o exemplo clássico de uma função recursiva:

int fatorial(int n)
{
  if(n == 0)
    return 1;
  else
    return n * fatorial(n - 1);
}

Note que a recursão termina quando o valor - que diminuia a cada recursão - chega a zero. Podemos usar o mesmo conceito usando especialização de templates, já que uma especialização funciona como um if para aquela condição específica:

int fatorial(int n)
{
  if(n == 0)
    return 1;
  else
    return n * fatorial(n - 1);
}

 
// template de fatorial
template<int n>
struct fatorial_t
{
  enum
  {
    value = n * fatorial_t<n - 1>::value
  };
};
 

// especialização para quando chegar a zero
template<>
struct fatorial_t<0>
{
  enum
  {
    value = 1
  };
};
 
int _tmain(int argc, _TCHAR* argv[])
{
  int f1 = fatorial(1);
  int f2 = fatorial(2);
  int f3 = fatorial(3);
  int f4 = fatorial(4);

 
  f1 = fatorial_t<1>::value;
  f2 = fatorial_t<2>::value;
  f3 = fatorial_t<3>::value;
  f4 = fatorial_t<4>::value;
 
  return 0;
}

A diferença entre fatorial e fatorial_t é que o primeiro é calculado em tempo de execução, e o segundo em tempo de compilação. Quando o programa está rodando, o valor de template_t<>::value é uma constante. E antes que você pergunte, não é necessário saber metaprogramação com templates para usar STL, só usei o tópico como um exemplo das utilidades da especialização de templates - essa sim, usada pela STL.

Acho que já vimos o que precisamos para entender a STL, no próximo post chegaremos onde realmente interessa.


Em 15/06/2006 19:52, por Rodrigo Strauss


  
 
 
Comentários
Mateus de Paula Marques | em 26/04/2008 | #
Kra, voce poderia explicar o porque de vc ter usado parametros para main nesse exemplo?? Ou me indicar um material p/ q eu possa entender??
Tbm fiquem com duvida na linha ASSERT(c3.a == 50 && c3.b == 50);
obrigado!
ps: Parabens, seus tutos tão nervosos, hehe!
Rodrigo Strauss | website | e-mail | em 28/04/2008 | #
Sobre os parâmetros do main, qualquer tutorial ou livro de C ou C++ explica isso. Esses parâmetros são os passado pela linha de comando para o programa.

O ASSERT é uma macro que dispara uma exceção caso a condição não seja satisfeita. Ou seja, se a expressão "c3.a == 50 && c3.b == 50" for falsa, o programa para no debugger e você pode ver o que deu errado.
Éverton | em 17/03/2009 | #
Rodrigo gostaria de dizer que oque sei sobre templantes aprendi aqui “e se aprendi errado é culpa sua ....... hahahhah” brincadeira.
Muito bom seus textos sempre que posso venho dar uma olhada nas novidades.
Guga =P | website | em 19/04/2010 | #
Amigo Rodrigo, vc poderia falar dos templates com parametros duplos tmb só por uma questão de completude, certo?

tipo

template<typename T1, typename T2)
void init(T1* p, const T2& value)
{
new(p) T1(value); // placement
}

int main()
{
double a;
init(&a, 2354); // aqui falharia se fosse template com parametro unico, pois o compilador n teria capacidade de inferir e forçaria o programador a designar explicitamente o tipo, exemplo: init<double>(&a, 2354);

cout << a << flush;
return 0;
}

abração e excelente material, concerteza vou muito aqui
Algo a dizer?
Nome:


Site:


E-mail:


Escreva o número vinte e seis:


 Não mostre meu e-mail no site, não serve pra nada mesmo...

Comentário





Os comentários devem ser sobre assuntos relativos ao post, eu provavelmente apagarei comentários totalmente offtopic. Se quiser me enviar uma mensagem, use o formulário de contato. E não esqueça: isso é um site pessoal e eu me reservo o direito de apagar qualquer comentário ofensivo ou inapropriado.
rebarba rebarba
  ::::