Il est préférable d'avoir des bases de métaprogrammation et d'avoir compris l'article sur l'effacement des types avant d'aborder ce document.
Alors que je prenais un café entre deux conférences d'un congrès, j'examinais un babillard où plusieurs proposaient des stratégies novatrices pour un langage ou l'autre. Java était à la mode, en particulier parce qu'il s'agit du langage le plus enseigné dans les universités ces jours-ci, et l'une des idées proposées était celle de déconstructeurs.
Là où un constructeur initialise un objet et un destructeur (dans les langages qui supportent le concept, donc excluant Java) en assure le bon nettoyage, un déconstructeur, suggéraient les auteurs (je paraphrase et j'interprète) serait une méthode offrant un uplet de valeurs que le code client pourrait explorer – ;une description de l'objet pris en pièces détachées, mais révélé par l'objet, de manière subjective et à sa convenance (à la limite, l'objet pourrait mentir).
L'idée m'est venue de représenter le concept en C++. J'ai utilisé, dans une version antérieure, un couple constitué d'une séquence de types effacés (comme le type peu_importe suggéré dans un autre article) et d'une liste de types, permettant de déduire sans danger les types effacés. Les deux étaient produits par l'objet interrogé, qui s'avérait responsable d'assurer leur cohérence.
La version proposée ici est un équivalent modernisé et simplifié grâce aux mécanismes de C++ 11, plus particulièrement grâce à std::tuple.
Un autre des éléments intéressants du schème qui sera proposé dans cette section est qu'il est général, au sens où on pourra appliquer des opérations arbitrairement complexes aux éléments déconstruits, ce qui permettra par exemple d'intégrer la déconstruction dans des séquences opératoires complexes. Pensez à de la sérialisation XML par exemple.
La déconstruction que nous allons définir utilisera une liste de types comme mécanisme d'aiguillage générique à l'analyse des types des valeurs obtenues. Nous utiliserons donc un uplet à titre de séquence de valeurs et de liste de types comme séquence de métadonnées aidant à comprendre les valeurs et à opérer efficacement sur elles.
L'idée de listes de types nous vient du brillant Andrei Alexandrescu, exposée dans son livre Modern C++ Design, mais a été mise au goût du jour avec les templates variadiques. Avec C++ 11, le type std::tuple permet de représenter les mêmes concepts que les listes de types. Nous utiliserons bien sûr le type standard plutôt qu'une version maison.
Si nous souhaitons démontrer la possibilité de déconstruire des objets, nous devrons appuyer cette démonstration par des objets concrets. La classe DemoSimple proposée à droite est un exemple d'objet déconstructible. La liste de types porte ici le nom de metadata. Utiliser un nom homogène pour tous les types déconstructibles peut aider à généraliser le schème proposé. |
|
À titre d'exemple aussi, la classe DemoGen proposée à droite montre un objet déconstructible générique sur la base d'un type T quelconque. Notez que la liste de types exposée en tant que groupe de métadonnées pourrait être arbitrairement longue. Nos deux exemples sont très simples et visent à démonter la faisabilité de la démarche, sans plus. Notez aussi que, bien que nos deux exemples exposent tous les types et les valeurs de tous les attributs, l'objet déconstructible pourrait faire une forme de filtrage et n'exposer qu'un sous-ensemble des types et des valeurs véritablement encapsulées (dans la mesure où les éléments des deux listes concordent, dans l'ordre). |
|
Pour manipuler les éléments d'un type déconstruit, la généricité est un outil précieux.
L'exemple d'opération que nous utiliserons ici sera une opération de projection sur un flux en sortie (notre programme principal, plus bas, utilisera simplement std::cout). Examinez le foncteur Afficher proposé à droite. Ce foncteur n'est pas une classe générique mais sa méthode operator(), elle, l'est. Puisque nous souhaitons que notre approche repose sur une métaphore applicable à tous les types, le point d'entrée des opérations appliquées sur les valeurs déconstruites devra être générique. Une fois ce point d'entrée atteint, l'implémentation peut être arbitrairement subtile (et inclure toutes les manoeuvres, génériques ou non, vous semblant pertinentes). |
|
Un équivalent plus concis, reposant sur des λ génériques, serait la fonction afficher() à droite. C'est bien sûr ce que nous utiliserons. La version sous forme de foncteur serait intéressante si nous choisissions de spécialiser différemment une même opération en fonction de certains types. |
|
L'application d'une opération générique quelconque aux diverses valeurs d'un tuple donné se fera comme suit :
Cette manœuvre passant par une liste d'initialisation tient à forcer la génération des applications dans un ordre croissant à partir de l'élément zéro. Sans elle, l'ordre d'application de la fonction f sur les éléments de tup n'est pas déterminé par le standard, ce qui peut surprendre. |
|
Un programme de démonstration serait celui proposé à droite, qui instancie deux objets de types distincts, puis affiche les valeurs de leurs éléments en les déconstruisant. La stratégie est très simple à utiliser et est, en même temps, extrêmement générale. |
|
EX00 – Raffinez l'opération d'affichage dans l'exemple proposé à même le texte pour que chaque valeur soit précédée de son type (de manière à faciliter une éventuelle désérialisation).
EX01 – Peut-on déconstruire un élément d'une séquence déconstruite, et ainsi de suite récursivement, si un objet déconstructible contient (et expose dans sa déconstruction) des objets eux-aussi déconstructibles? Si oui, comment faire? Sinon, pour quelle raison?
EX02 – Donnez un exemple d'objet déconstructible pour lequel la liste de types (et la liste de valeurs) serait vide.
EX03 – Exprimez de manière générique la sérialisation XML d'objets déconstructibles.
EX04 – La technique proposée ici opère sur des copies des attributs. Pourrait-on utiliser cette technique de manière à exposer des indirections (par des références ou des suppléants, ou Proxy Objects) pour appliquer des séquences d'opérations mutant l'état de l'objet? Si oui, montrez comment y arriver, et explorez les conséquences de cette réalité. Sinon, expliquez pourquoi.
EX05 – Comment peut-on en arriver à un schème semblable si le type à déconstruire est polymorphique? Notez qu'il est hautement probable que RTTI (ou un équivalent) doive être réintroduit dans le portrait.
EX06 – Y aurait-il des avantages à rendre polymorphique la méthode deconstruct() des objets déconstructibles? Justifiez votre réponse?
EX07 – Montrez comment il serait possible, dans une certaine mesure, de rendre déconstructibles a posteriori des types ne nous appartenant pas (dont ceux des bibliothèques standards du langage) en appliquant la technique des traits. Montrez que cette approche permet aussi d'intégrer les types primitifs au modèle.
EX08 – Évaluez la somme des valeurs numériques déconstruites d'un objet déconstructible donné. Il est possible que la séquence déconstruite comprenne des valeurs non numériques; ces valeurs doivent être escamotées par votre algorithme. Le type résultant doit être double.
EX09 – Reprenez l'exercice précédent mais faites en sorte que le type de la somme soit le type numérique le plus approprié aux types véritablement dans la liste de types (p. ex. : si la liste ne comprend que des short, alors évaluer la somme sur un int suffit probablement).