Os conceitos (concepts) já estão aprovados para o novo C++ e foram o tema do primeiro post desta série. Nele, apresentei uma introdução básica sobre assunto. Em particular, quais são as motivações para o formalismo estabelecido pelos conceitos. Agora, é hora de entender melhor como eles funcionam.
Deve estar claro para você que os conceitos representam a interface pela qual estruturas de dados são acessadas e/ou manipuladas. Seus principais componentes são os tipos associados e funções associadas. (Outros itens que podem fazer parte da especificação de um conceito serão discutidos em breve.) Inicialmente, vamos nos concentrar na estrutura básica de um programa baseado em conceitos. Para isso, será utilizado o exemplo clássico: LessThanComparable.
Se você tem experiência com a STL é bem provável que tenha passado pela especificação desse conceito no site da SGI1. Porém, se você não tem experiência com programação genérica, mesmo tendo esbarrado na especificação da SGI também é bastante provável que não tenha dado muito atenção ao conteúdo lá descrito. Mas não importa! O que você precisa saber neste momento é que para que determinado tipo modele ou satisfaça o conceito LessThanComparable (“comparável por menor”) é necessário que um operator<
compatível esteja disponível. Consequentemente, a definição desse conceito deve incorporar tal requisito.
concept LessThanComparable<typename T> { bool operator<(T const& x, T const& y); } [/sourcecode] O trecho de código acima é simples. Ele diz a compilador que existe um conceito chamado <em>LessThanComparable</em> e que esse conceito exige uma <em>função associada</em>, um <code>operator<</code> para o tipo <code>T</code>. Ou seja, para que um tipo <code>T</code> seja considerado <em>LessThanComparable</em> deve ser possível utilizá-lo em um <code>operator<</code> com dois parâmetros. Com o conceito definido, podemos, então, criar <em>mapas de conceitos</em> que mapeam estruturas de dados diversas em conceitos específicos. No nosso caso, os tipos primitivos <code>int</code> e <code>double</code> podem ser comparados por menor com o próprio <code>operator<</code> built-in da linguagem C++. Logo, basta dizer ao compilador que tais tipos <em>satisfazem</em> o conceito <em>LessThanComparable</em>. Isso é feito com a palavra-chave <code>concept_map</code>. concept_map LessThanComparable<int>{ } concept_map LessThanComparable<double> { }
Neste momento, é bem provável que você esteja se perguntando: Já que int
e double
são naturalmente comparáveis com o operator<
, por quê preciso mapeá-los explicitamente? A resposta é que você não precisa! Mas para isso é necessário modificar ligeiramente a definição do conceito LessThanComparable. A idéia é deixar claro para o compilador que esse conceito é automático. Portanto, os mapeados podem ser inferidos implicitamente. O compilador está livre para procurar definições compatíveis do operator<
ou de qualquer outra função associada ao conceito em determinado escopo.
auto concept LessThanComparable<typename T> //Note o auto. { bool operator<(T const& x, T const& y); } [/sourcecode] A definição de um conceito como automático libera o programador de ter que indicar explicitamente quais os tipos de dados que o satisfazem. Isso inclui tantos os tipos primitivos quanto os tipos definidos pelo usuário. Por exemplo, se você criou uma classe <code>book</code> e também criou um <code>operator<</code> que compara <code>book</code>s, esta classe é considerada um modelo de <em>LessThanComparable</em> sem a necessidade de um mapeamento explícito. struct book { int num_pages_; //... }; bool operator<(book const& b1, book const& b2) { return b1.num_pages_ < b2.num_pages_; } //O mapeamento abaixo não é necessário! //concept_map LessThanComparable<book> { }
Por outro lado, se não existe um operator<
para book
s, mas ainda sim é desejável que a classe seja LessThanComparable, o mapeamento explícito deve ser fornecido. No próprio mapa de conceito é possível especificar como a comparação de menor é realizada para a classe book
.
struct book
{
int num_pages_;
//…
};
//Não há operator<. Logo, o mapeamento é necessário.
concept_map LessThanComparable
{
bool operator<(book const& b1, book const& b2)
{
return b1.num_pages_ < b2.num_pages_;
}
}
[/sourcecode]
Seja através de conceitos automáticos (com a palavra-chave auto
) ou não, a moral da história é que o compilador deve ser capaz de encontrar implementações concretas que satisfaçam os requisitos associados ao conceito. Não faz diferença se o mapeamento é feito implicitamente ou explicitamente por um mapa de conceito, como ilustram, respectivamente, os dois últimos trechos de código acima. Durante a compilação determinados tipos (primitivos ou definidos pelo usuário) serão considerados como modelos de conceitos e outros não.
Se você leu a primeira parte deste texto deve se lembrar que o problema da função print
era o fato de não haver uma garantia de que o tipo T
parametrizado possuísse um operator<<
válido. Trazendo o problema para o contexto deste post e fazendo uma analogia, gostaríamos de implementar uma função minimum
, por exemplo, na qual houvesse a garantia de que para o tipo T
parametrizado sempre existisse um operator<
compatível. Exatamente para esse propósito que os conceitos são extremamente úteis.
O template de função minimum
abaixo impõe uma restrição sobre T
. Tecnicamente, ela cria um contexto restrito no qual os requisitos do conceito especificado devem ser obrigatoriamente satisfeitos para o tipo de dado parametrizado. Neste caso, apenas modelos de LessThanComparable são aceitos em minimum
.
template
requires LessThanComparable
T const& minimum(T const& a, T const& b)
{
return a < b ? a : b;
}
[/sourcecode]
A definição de minimum
é muito parecida com a forma tradicional que estamos acostumados. No entanto, há um pequeno detalhe que faz uma enorme diferença: Sob o parâmetro T
é aplicada uma cláusula de restrição dizendo que apenas tipos que satisfazem LessThanComparable são considerados válidos. Um template restrito de função como minimum
traz duas grandes vantagens para programadores:
- Contrato para autores de templates: Em um template restrito os autores de templates não podem utilizar expressões, tipos associados ou qualquer relação de dependência sobre o tipo parametrizado que não esteja especificada no conceito em questão. Isso quer dizer que se o autor de
minimum
tivesse a brilhante idéia de implementar o corpo do template de função com a expressão!(a >= b)
ao invés dea < b
ele receberia um erro de compilação. Portanto, autores de templates estão agora mais seguros e apoiados em um mecanismo formal para poderem estabelecer os requisitos de suas obras-primas. - Contrato para usuários de templates: O grande benefício para os usuários de templates é que eles têm uma maneira clara e objetiva para conhecerem os requisitos mínimos a serem oferecidos por tipos de dados submetidos como argumentos a templates de classes e/ou funções. Se mesmo assim ainda forem cometidos erros, o compilador gerará mensagens de erro sucintas e diretas a respeito de qual requisito do conceito não foi satisfeito. A detecção do erro não mais acontecerá na etapa de compilação que realiza a instanciação de templates. Agora, a detecção do erro será imediata.
O programa abaixo exemplifica a discussão deste texto.
struct book
int num_pages_;
//…
};
//Mapeando book.
concept_map LessThanComparable
bool operator<(book const& b1, book const& b2) {
return b1.num_pages_ < b2.num_pages_;
}
}
struct magazine {
int num_pages_;
};
//Nenhum mapeamento ou operator< para magazine.
template
requires LessThanComparable
T const& minimum(T const& a, T const& b) {
return a < b ? a : b;
}
int main() {
book b1, b2;
//...
bool rb = minimum(b1, b2);
magazine m1, m2;
//...
bool rm = minimum(m1, m2); //Opa! Erro!
//magazine não é LessThanComparable!
return 0;
}
[/sourcecode]
Os conceitos são um recurso poderoso do novo C++. O objetivo deste post é simplesmente introduzir os princípios básicos. Ao longo do tempo pretendo mostrar toda a riqueza sintática e semântica contida em um conceito. Você verá, por exemplo, como modificar indiretamente (através de mapas de conceitos) o comportamento de uma classe sem sequer tocá-la. No próximo post continuarei com os fundamentos dos conceitos C++. Para os mais apressados, o draft atual pode ser encontrado no site ISO.
Leandro T. C. Melo
(1) O novo padrão C++ incorpora um número enorme de especificações de conceitos. No entanto, há conceitos no site da SGI que não foram padronizados. Há também alguns conceitos não possuem exatamente os mesmos requisitos de seus correspondentes da SGI.
Deixe um comentário