Pages

jeudi 20 décembre 2012

Taguer vos classes, cataloguées les

Le C++ a l'avantage de faire de la surcharge de fonction et permet ainsi de spécifier des algorithmes selon des critères> Ici ce seront des `tags`.

Comme exemple je vais utiliser les tags présents dans les itérateurs de la stl et une implémentation de la fonction std::advance().

Première implémentation

La fonction std::advance() permet d'incrémenter un itérateur de N éléments (ou décrémenter si N est négatif). D'après cette description, un premier algorithme peut être émis:

template<class InputIt, class Distance>
void advance(InputIt& it, Distance n)
{
  if (n < 0){
    while (n++)
      --it;
  }
  else {
    while (n--)
      ++it;
  }
}

Optimisation

Maintenant que se passe-t-il quand l'itérateur est un pointeur ? Réponse: Une boucle.
Ne serait-il pas préférable de faire it += n !?
C'est là que les tag rentrent en jeux.

Utilisation des tags

Les itérateurs possèdent des types prédéfinis qui sont: pointer, value_type, reference, difference_type et iterator_category. C'est ce dernier qui correspond au tag de l'itérateur.

En réalité un tag est une classe vide, cela suffit, le type est porteur de l'information. Les tags des itérateurs possèdent aussi une hiérarchie et certains sont donc hérités.

Pour récupérer le type contenu dans un itérateur il faut passer par std::iterator_traits<>, cela permet de fonctionner même avec un type scalaire comme le pointeur qui ne possède pas de type interne.

Le tag d'un pointeur étant std::random_access_iterator_tag voici une implémentation:

template<class It>
class std::iterator_traits<It>::iterator_category
iterator_category(const It&)
{ return typename std::iterator_traits<It>::iterator_category(); }

template<class RandomIt, class Distance>
void advance(RandomIt& it, Distance n, std::random_access_iterator_tag)
{ it  += n; }

template<class InputIt, class Distance>
void advance(InputIt& it, Distance n)
{ advance(it, n, iterator_category(it)); }

Bien sûr, on change aussi le prototype de la précédente implémentation.

template<class InputIt, class Distance, class Tag>
void advance(InputIt& it, Distance n, Tag);

Problème avec ForwardIterator

Toutefois un problème persiste, lorsqu'un ForwardIterator est utilisé, il n'y a pas d'opération de décrémentation, et la compilation ne se fait pas. Il faut de nouveau changer notre première implémentation pour qu'elle ne fonctionne qu'avec bidirectional_iterator_tag et une implémentation généraliste qui fait une incrémentation.

template<class InputIt, class Distance, class Tag>
void advance(InputIt& it, Distance n, Tag)
{
  while (n--)
    ++it;
}

template<class InputIt, class Distance>
void advance(InputIt& it, Distance n, std::bidirectional_iterator_tag)
{
  if (n < 0){
    while (n++)
      --it;
  }
  else {
    while (n--)
      ++it;
  }
}

Conclusion

L'utilisation de tag combinée avec la surcharge de fonction est plus simple que la spécialisation de template et permet un meilleur ciblage dans les algorithmes utilisés.

Good conclusion :D