Continuando a série de posts sobre conceitos (concepts) no C++0x, agora é hora de apresentar outro elemento importante. No último post, expliquei como especificar um contexto restrito no qual apenas tipos LessThanComparable (comparáveis por menor) são permitidos. Tais tipos devem suportar o operator< que recebe dois parâmetros e retorna um bool. Porém, nem sempre trabalhamos com funções associadas que operam somente sobre tipos primitivos. Inclusive, em muitos casos nem sequer sabemos exatamente quais os tipos dos dados em questão. Para resolver esses e outros problemas existem os tipos associados.
O objetivo dos tipos associados é justamente introduzir abstrações pelas quais podemos suportar funções associadas de maneira natural. Para ilustrar, considere o exemplo do post anterior no qual criei uma classe chamada book. Suponha que essa classe faça parte de uma aplicação que modela o sistema de uma livraria. Nela, todos os itens como livros, revistas, jornais ou similares são caracterizados como sendo materias de leitura.
A aplicação da livraria, assim como qualquer outra, possui regras de negócio que devem ser aplicadas a todos os materiais de leitura. Mas como estamos no mundo da programação genérica, ao invés de fazer com que todos eles herdem de uma classe base comum farei com que todos modelem um conceito comum, chamado ReadableMaterial.
Para que um item seja considerado um ReadableMaterial ele precisa satisfazer alguns requisitos como, por exemplo, saber o seu número de páginas. No caso da classe book do post anterior assumi que essa informação era armazenada em um membro do tipo int. No entanto, para promover um tratamento uniforme entre os variados materiais de leitura é necessário adotar uma abstração genérica que represente o tipo de dados que cada um deles utiliza para armazenar seu número de páginas. Afinal de contas, nessa aplicação hipotética, enquanto os livros utilizam um int os jornais e revistas utilizam um short. Já as enciclopédias utilizam um std::size_t. Além disso, pode aparecer um novo material de leitura que utilize um tipo de dados personalizado, definido pelo próprio usuário. (Lembre que isso é apenas um exemplo de caráter didático.) Portanto, a solução é especificar, além da função associada, um tipo associado ao conceito ReadableMaterial.
Uma outra informação que gostaria de ter sobre os materiais de leitura é se determinada página contém alguma ilustração. Assim como o número de páginas, esse requisito pode ser introduzido no conceito através de uma função associada. Com isso, a definição de ReadableMaterial fica conforme o código abaixo.
concept ReadableMaterial <typename T>
{
typename page_size_type;
page_size_type num_pages(T const&);
bool is_illustrated_page(T const&, page_size_type);
}
Agora, a próxima etapa é justamente criar mapas de conceito entre ReadableMaterial e as classes que o modelam, como book, magazine, newspaper, entre outras. Abaixo, mostro apenas para book, mas os outros seriam similares. Note que o conceito não requer as funções associadas num_pages e is_illustrated_page como membros do tipo modelo (que, de fato, pode ser feito). Logo, preciso realizar o mapeamento explícito dessas funções (no caso, utilizando as próprias funções membro de book).
class book
{
public:
int num_pages() const { return num_pages_; }
bool is_illustrated_page(int page_num) const { /*...*/ }
private:
int num_pages_;
};
concept_map ReadableMaterial<book>
{
typedef int page_size_type;
int num_pages(book const& b)
{ return b.num_pages(); }
bool is_illustrated_page(book const& b, int page_num)
{ return b.is_illustrated_page(page_num); }
}
Tendo o conceito e seus respectivos modelos definidos, posso implementar uma função genérica simples que conta o número de páginas com ilustrações de um ReadableMaterial. Preste atenção no código e tente identificar se ainda está faltando algo nesta abordagem.
template <typename T>
requires ReadableMaterial<T>
ReadableMaterial<T>::page_size_type count_illustrated_pages(T const& t)
{
ReadableMaterial<T>::page_size_type count = 0;
for (ReadableMaterial<T>::page_size_type i = 0; i < num_pages(t); ++i)
if (is_illustrated_page(t, i))
++count;
return count;
}
Provavelmente, os leitores que já captaram a essência da programação baseada em conceitos detectaram pequenos problemas. No primeiro post da série, tentei deixar claro que uma grande vantagem desse paradigma é exatamente a imposição contratual de certas regras, tanto para o autor quanto para o usuário de templates. Eu, como autor de count_illustrated_pages, violei parte desse contrato. O motivo é o seguinte: Informei aos usuários que tipos que modelam ReadableMaterial poderiam ser utilizados na função acima. Porém, em sua implementação utilizei construções sintáticas que não são requisitos desse conceito. Ou seja, count_illustrated_pages depende que page_size_type satisfaça requisitos que não são exigidos em ReadableMaterial. São eles:
- Construção a partir de um inteiro;
- Comparação por menor;
- Operador de pré-incremento;
- Construtor de cópia;
- Destrutor.
Esses pequenos itens não são motivo de dores de cabeça. Há uma forma simples de fazer o exemplo funcionar. Basta adicionar os requisitos acima ao tipo associado page_size_type de ReadableMaterial. Felizmente, há um conceito chamado ArithmeticLike no novo padrão C++ que já os encapsula. Portanto, há basicamente duas formas de contornar a situação. A primeira delas consiste de adicionar a restrição de ArithmeticLike sobre page_size_type na função count_illustrated_pages, conforme mostrado abaixo. (Note a notação simplificada na qual especifico T como sendo um ReadableMaterial ao invés de um typename convencional.)
template <ReadableMaterial T>
requires ArithmeticLike<T::page_size_type>
T::page_size_type count_illustrated_pages(T const& t)
{
T::page_size_type count = 0;
for (T::page_size_type i = 0; i < num_pages(t); ++i)
if (is_illustrated_page(t, i))
++count;
return count;
}
A segunda delas, que considero a melhor opção, é adicionar a restrição de ArithmeticLike sobre page_size_type diretamente em ReadableMaterial. Nesse caso, não é necessário colocar a restrição de ArithmeticLike sobre a função, já que agora ela é parte integral do próprio conceito.
concept ReadableMaterial <typename T>
{
typename page_size_type;
page_size_type num_pages(T const&);
bool is_illustrated_page(T const&, page_size_type);
requires ArithmeticLike<page_size_type>; //Novo requisito!
}
Até a próxima!
Leandro T. C. Melo