La métaprogrammation avec des patrons est une technique de programmation informatique. dans laquelle les patrons sont utilisés de sorte que le compilateur, lors de la compilation du code, exécute un programme. Ces programmes peuvent générer des constantes ou des structures de données.

Cette technique est utilisée principalement dans le langage de programmation C++.

Calcul de constantes L'exemple suivant de calcul de factorielle avec récursion illustre ce qu'est la « programmation lors de la compilation ».

En C++, une fonction factorielle peut être écrite récursivement comme suit :

En utilisant la métaprogrammation avec des patrons, on peut écrire :

La solution de la métaprogrammation avec des patrons utilise la spécialisation de patron pour terminer la récursion. Bien que ces deux solutions soient similaires, dans le deuxième cas, Factorielle<4>::valeur est calculé lors de la compilation. Cela implique que Factorielle<x>::valeur ne peut être utilisé que si x est connu lors de la compilation, c'est-à-dire si x est une constante (à partir de c++11 une constexpr) ou le résultat d'un appel à sizeof().

En D, le patron factorielle ressemblerait à :

La métaprogrammation avec des patrons a des utilisations pratiques malgré son apparence maladroite. Elle peut être utilisée pour créer des classes vecteur à n dimensions quand n est connu à la compilation. L'avantage par rapport à un vecteur à n dimensions traditionnel est que les boucles peuvent être déroulées, ce qui produit un code très optimisé.

Considérons l'exemple de l'addition. Une addition pour un vecteur à n dimensions pourrait être écrite ainsi :

Quand le compilateur instancie la fonction patron définie ci-dessus, le code suivant est produit :

L'optimiseur du compilateur est capable de dérouler la boucle for car le paramètre dimension du patron est une constante connue à la compilation. Pour une véritable implémentation de cette technique, voir .

En C++, la métaprogrammation avec des patrons est Turing-complète, ce qui signifie que n'importe quel calcul exprimable par un programme peut être calculé par un métaprogramme à base de patrons.

Les métaprogrammes à base de patrons n'ont pas de variables mutables, c'est-à-dire qu'aucune variable ne peut changer de valeur une fois initialisée. Cela a pour conséquence que contrairement au code à l'exécution, la métaprogrammation à base de patrons est une forme de programmation fonctionnelle. Pour cette raison, l'analyse de flot dans les métaprogrammes est faite par le moyen de la récursion, comme vu plus haut.

Initialisation de données En C++, la métaprogrammation avec des patrons permet d'initialiser une donnée comme un tableau ou une structure de données complexe. L'exemple suivant illustre le remplissage d'un tableau dont les 32 premières valeurs sont le carré de l'index, et les valeurs suivantes sont l'index.

Le template est utilisé pour faire calculer la valeur d'un élément du tableau :

Comme dans l'exemple de factorielle, pour récupérer la , il suffit d'écrire ValeurTab<10>::valeur. Ainsi le remplissage d'un tableau se ferait de cette manière :

Pour faciliter le remplissage d'un grand tableau, on peut utiliser des macros classiques:

Ainsi pour remplir un tableau de 100 éléments en respectant l'algorithme du patron de classe, il suffit d'écrire :

Contrôle sur la compilation D'abord, quelques classes :

Il se peut que, dans un programme, un objet de classe UneClasseC soit manipulé, lors de divers appels de fonctions (ou méthodes), comme un objet de classe UneClasseA afin de profiter des avantages du polymorphisme. Cependant, il se peut que l'on ait un traitement spécifique selon la classe réelle de l'objet. Par exemple, ceci arrive fréquemment pour l'affichage graphique d'un objet lorsque le programme est découpé selon le concept MVC. Cette technique est un motif de conception appelé visiteur. Classiquement, en C++, cela se fait par une cœrcition descendante (Downcasting en anglais). Il suffit alors de faire les tests :

Un problème peut se poser lors de la programmation d'un nouvel objet (UneNouvelleClasseD). En effet, il faut penser à modifier toutes les parties de codes qui font ce genre de traitement. L'erreur ne sera visible que lors de l'exécution du programme, et il faut que cette exécution passe dans cette portion de code pour observer l'erreur.

Ici, la métaprogrammation permet de rendre le programme impossible à compiler si l'on rajoute un type qui n'est traité nulle part.

Pour cela, il faut établir une liste de types qui permettra de vérifier si tous les traitements de chaque type ont été programmés.

Une liste de types se présentera sous une forme récursive : une liste contient un type et une sous-liste. La liste se termine par le type ListeFin. Par exemple:

Il suffit alors de créer une classe abstraite qui sera spécialisée avec cette liste de types. Cette classe sera rendue non abstraite en implémentant une méthode virtuelle pure pour chaque type (une méthode par traitement spécifique). Ainsi, l'oubli de l'implémentation de la méthode d'un type de la liste, génèrera une classe abstraite. Une classe abstraite ne pouvant pas être instanciée, le compilateur retournera une erreur lors de la création de l'objet Visiteur. Il suffira alors d'ajouter l'implémentation de la méthode de traitement du nouveau type, pour enlever le comportement abstrait de la classe et permettre une instanciation. Le compilateur peut alors compiler le programme et aucune méthode d'implémentation ne sera oubliée.

Il faut donc créer une classe abstraite visiteur avec une liste quelconque de types :

Ensuite, il faut créer la liste de types spécifiques.

Enfin, il faut créer la classe qui implémentera tous les traitements spécifiques :

Ainsi, pour ajouter un nouveau type, il faut l'ajouter dans la liste de types UneListeClasses, et pour que le programme compile, il faudra ajouter l'implémentation de la méthode visite pour le nouveau type.

Avantages et inconvénients de la métaprogrammation à base de patrons

Compromis entre la compilation et l'exécution : comme tout le code sous forme de patron est traité, évalué et généré à la compilation, la compilation prendra plus de temps tandis que le code exécutable pourra être plus efficace. Bien que cet ajout soit généralement minime, pour des gros projets, ou pour des projets qui utilisent des patrons en grande quantité, cela peut être important. Programmation générique : la métaprogrammation à base de patrons permet au programmeur de se concentrer sur l'architecture et de déléguer au compilateur la génération de n'importe quelle implémentation requise par le code client. Ainsi, la métaprogrammation à base de patrons peut être accomplie via du code vraiment générique, facilitant la minimisation et la maintenabilité du code. Lisibilité : la syntaxe et les idiômes de la métaprogrammation à base de patrons sont ésotériques par rapport à du C++ conventionnel, et des métaprogrammes avancés peuvent être très difficiles à comprendre. Les métaprogrammes peuvent ainsi être difficiles à maintenir pour des programmeurs peu rompus à cette technique. Portabilité : à cause de différences dans les compilateurs, le code reposant fortement sur de la métaprogrammation à base de patrons (en particulier les nouvelles formes de métaprogrammation) peuvent avoir des soucis de portabilité.

Références

Catégorie:Programmation informatique