Appel conditionnel de fonction selon la validité d'une expression

Article suivant: Implémentation d'un magasin de type
Article précédent: Réduire l'empreinte mémoire d'une agglomération de types

L’approche suivante consiste à vérifier qu’une fonction (membre ou statique) est appelable dans le but de l’utiliser, ou, à défaut, fournir implémentation générique. De manière plus générale, la méthode présentée ici s’applique à toutes expressions.

Appeler T::sort si possible, sinon std::sort(begin(T), end(T))

L’exemple va se faire sur la classe std::list qui n’est pas triable avec std::sort, mais possède une fonction membre sort(). Ainsi que sur std::vector qui, inversement, n’a pas de fonction membre sort(), mais fonctionne avec std::sort.

La méthode est simple et consiste à créer 2 fonctions: une pour vérifier si une expression est valide (ici x.sort()) et une autre en cas d’échec.

Seulement, qui dit 2 fonctions dit 2 prototypes. Leur prototype doit être légèrement différent mais compatible avec les mêmes valeurs d’entrée pour appeler la seconde si la première échoue (principe du SFINAE).

Pour vérifier l’expression, seuls 2 mots clef existent: sizeof et decltype. Cette procédure est donc possible avant C++11, même si sizeof requière un peu d’enrobage.

#include <algorithm>

//avec decltype
template<class Container>
auto dispatch_sort(Container& c, int)
-> decltype(void(c.sort())) // force decltype au type void
{ c.sort(); }

template<class Container>
void dispatch_sort(Container& c, unsigned)
{
  using std::begin;
  using std::end;
  std::sort(begin(c), end(c));
}

template<class Cont>
void sort(Cont& c)
{ dispatch_sort(c, 1); }

La fonction sort appel dispatch_sort avec un int (la valeur n’importe pas, seul le type compte). Comme la seule différence des 2 fonctions dispatch_sort est le premier paramètre, le prototype avec un int correspond parfaitement.

Si une fonction membre sort existe, alors l’expression dans decltype est valide et la fonction appelé. Dans le cas contraire, le compilateur cherche une fonction avec des paramètres pouvant être compatibles. Le int pouvant être converti en unsigned, le compilateur se rabat sur le second prototype qui fait appel à std::sort.

Le point clef étant de mettre toutes les informations dans le prototype. J’aurais par exemple pu mettre decltype dans un paramètre initialisé avec une valeur par défaut (f(int, decltype(xxx)* = 0);, mais il faudra probablement ajouter std::remove_reference car un pointeur sur une référence n’est pas permis).

Programme de test

#include <iostream>
#include <vector>
#include <list>

int main()
{
  std::vector<int> v({2,6,4});
  std::list<int>   l({2,6,4});

  sort(v);
  sort(l);

  for (auto i : v) std::cout << i << ' ';
  std::cout << '\n';
  for (auto i : l) std::cout << i << ' ';
}

Résultats:

2 4 6
2 4 6

implémentation avec sizeof (pre-C++11)

J’ai indiqué qu’il été possible d’utiliser sizeof à la place de decltype. Voici comment:

template<std::size_t, class T = void>
struct dispatch_result_type
{ typedef T type; };

template<class T>
T declval();

template<class Container>
typename dispatch_result_type<
  sizeof(void(declval<Container&>().sort()),1)
>::type
dispatch_sort(Container& c, int)
{ c.sort(); }

Le ,1 de sizeof(xxx,1) peut dérouter mais est requis si l’expression xxx retourne void. Comme void n’est pas vraiment un type, il ne fonctionne pas avec sizeof et il faut donc lui fournir autre chose. Il faut bien comprendre qu’ici xxx,1 est une seule expression et non pas 2 paramètres.

Bien que très peu probable, si j’ai mis void(yyy), c’est pour prévenir la surcharge de l’opérator ‘,’ sur le type de retour retourné par yyy (car cet opérateur peut lui-même retourner un void).

sizeof ne donne pas l’information sur le type de retour mais une valeur, il est couplé à dispatch_result_type qui prend en second paramètre template le type de retour (void par défaut). Quant à declval, c’est le même principe que celui de la SL.

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