Pages

jeudi 2 juillet 2015

Implémentation d'un magasin de type

Ce que j'appelle ici un magasin de type n'est autre qu'un std::tuple où les types ne sont présents qu'une seule fois. Une espèce de set version tuple en somme.

Je me suis servi de ce type de structure à 2 reprises.

Une fois pour manipuler de façon similaire des types hétérogènes sans la lourdeur de std::tuple. Il faut dire aussi que j'étais en C++11 et que dans cette norme std::get<Type>() n'existe pas.

L'autre fois dans une fonction variadique qui distribue les valeurs vers différentes fonctions. Le but étant de ne pas se soucier de l'ordre des paramètres, certains étant optionnels.

std::tuple fait plutôt bien le boulot, mais possède plusieurs inconvénients pour ce cas de figure.

  • Aucune erreur de compilation si un type est présent 2 fois (et c'est normal pour un tuple).
  • Prend beaucoup de mémoire et de temps de compilation (osef !)

Je pourrais faire l'apologie de RapidTuple histoire de me faire mousser (le projet contient un tuple_set), mais en fait non, on peut faire encore plus simple en 10 lignes de code :).
Bon ok, 3 lignes.
Mais 10 pour rendre pratique ;).

Planter la compilation quand un type est en doublon.

Le C++ dispose déjà d'un mécanisme interne qui vérifie et hurle au scandale si un type doublon existe. J'ai nommé l'héritage.

Seulement, un héritage direct n'est pas possible avec les types scalaires, il faut un intermédiaire.

template<class T> struct item { T x;  }; 

template<class... Ts>
struct store : item<Ts>... {}; 

Avec cette implémentation, des petits malins pourraient faire de la pseudo-duplication de type en y ajoutant des qualificatifs, store<int, int const> par exemple.

On peut être tolérant ou devenir un tyran sans pitié en empêchant cela.

template<class... Ts>
struct tyrannical_store_impl : store<std::remove_cv_t<Ts>...> {
  using type = store<Ts...>; 
}; 

template<class... Ts>
using tyrannical_store = typename tyrannical_store_impl<Ts...>::type; 

Le store tyrannique est construit en 2 étapes, car un alias direct sur un store épuré ne permet pas de garder les qualificatifs.

Piocher dans le magasin.

Piquer un élément du magasin est une affaire de cast. Un simple static_cast.

store<int, char> my_store; 

static_cast<item<int>&>(my_store).x; 

En mettant des opérateurs de cast dans la classe item, plus besoin de préciser cette dernière avec le static_cast.

template<class T> struct item {
  explicit operator T & () noexcept { return x_;  }
  explicit operator T const & () const noexcept { return x_;  }
private:
  T x_; 
}; 
store<int, char> my_store; 

static_cast<int&>(my_store); 

Petit bémol toutefois, cela ne permet pas d'enlever l'ambiguïté pour un type qui diffère uniquement par son qualificatif.

store<int, int volatile> my_store; 

static_cast<int volatile&>(my_store);  // 'store<int, volatile int>' to 'volatile int&' is ambiguous

Ce qu'il manque

  • Les constructeurs, évidemment.
  • Une fonction get<Type>() pour un parallèle avec la STL.
  • Une fonction pour boucler sur chaque item (apply_from_store ?).
  • Et sûrement d'autres.

J'ai mis tout ça dans un repo au nom provisoire (falcon.store).

Aucun commentaire: