Taguer vos classes, cataloguées-les

Article suivant: La vis cachée de getopt
Article précédent: Zsh et le danger des modificateurs

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 tags entrent en jeu.

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>
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

Les tags sont un moyen simple de spécifier les comportements liés à une classe. On peut les voir comme une liste d’interface disponible pour cibler précisément ce qu’il est possible de faire ou ne pas faire.

Commentaires

Aucun commentaire pour le moment :'(

Le système de commentaire passe par les issues de github et aucun n'est associée au billet. Vous pouvez faire votre commentaire dans une issue qui a comme titre celui du billet. Je me chargerai de les associer.

Revenir en haut