Pages

samedi 20 juillet 2013

Utilisation de swap et des fonctions utilitaires en général

Quitte à trouver un meilleur nom, ce que j'appelle fonction utilitaire est toute fonction libre de conversion (to_string, etc) ou d'accesseur externe à une classe (get, begin, end, etc).

Ce sont toutes des fonctions qu'on peut trouver en C++11. La seule fonction utilitaire qui me vient à l'esprit en C++03 est swap (fonction qui échange le contenu de 2 variables). Pour info, l'en-tête de swap est passé de <algorithm> en C++03 à <utility> en C++11.

Il arrive qu'un jour ou l'autre on veuille faire une surcharge de swap pour un objet particulier. À ce moment 2 choix s'offrent: en faire une fonction libre dans le namespace où se trouve la classe ou dans le namespace std. Évidemment, le meilleur choix est le premier.

Et là, pour toutes les personnes qui ont en horreur le `using namespace std` ou travaillant dans les .h, un problème va se poser. Comme swap se trouve dans le namespace de la std, il est logique de faire std::swap(...). Mais à ce moment, la fonction swap spécialisée dans le namespace de la classe n'est pas utilisée...

#include <iostream>
#if __cplusplus >= 201103L
# include <utility>
#else
# include <algorithm>
#endif

namespace my {
  struct A{};
  void swap(A&, A&)
  { std::cout << "ok\n"; }
}

int main()
{
  my::A a, b;
  std::swap(a,b);
}

N'affiche rien... :/.

Par contre ce qui suit affiche "ok".

int main()
{
  my::A a, b;
  swap(a,b);
}

Comme une fonction swap prenant des my::A existe elle est utilisé. Cela est possible car la fonction swap fait partie du namespace my et que les variables a et b sont des instances d'une classe du même namespace.

Mais ceci ne compile pas.

int main()
{
  int a, b;
  swap(a,b);
}

Donc, d'un côté on a un swap générique dans la std et de l'autre un swap spécialisé dans my.
Et surtout, 2 syntaxes différentes.

Dans un contexte générique (typiquement des templates) et de maintient de code ; 2 formes n'est pas acceptable.
Il faudrait que std::swap soit utilisé si aucun swap spécialisé n'existe.

Pour ce, on 'déplace' std::swap dans le scope courant avec using et grâce l'ADL (Argument-dependent lookup) le compilateur appellera la bonne fonction.

int main()
{
  using std::swap;
  {
    my::A a, b;
    swap(a,b); //affiche ok
  }
  {
    int a, b;
    swap(a,b); //compile
  }
}

Et ceci s'applique pour toutes les fonctions libres.

L'article suivant présente une solution pour éviter l'utilisation systématique de using.

2 commentaires:

Anonyme a dit…

Bonjour,
Merci pour l'astuce !

Petite remarque : il manque my:: devant A ;)

Jonathan Poelen a dit…

Ah bah oui Oo

Je m’empresse de corriger ça, merci :).