Les templates (2/2)
Template Meta-Programming
Ce terme (TMP) est souvent utilisé pour désigner l'exécution de code à la compilation. Cela peut être vu comme un langage à part entière au sein du C++, où les classes sont des fonctions : leurs paramètres template sont les entrées, et leur contenu forment leur sortie.
Ce langage possède beaucoup de propriétés des langages fonctionnels, notamment :
Aucune variable ne peut être modifiée
On ne dispose pas de boucles (on utilise la récursion)
Les fonctions sont pures (elles retournent toujours la même chose avec les mêmes arguments)
Cela implique qu'aucun effet de bord n'est possible
Les branchements sont fait par pattern matching sur les types
Certaines évaluations sont paresseuses (instanciées uniquement lorsqu'on a besoin d'elles)
Ces propriétés demandent de changer de paradigmes de programmation pour être à l'aise en TMP, et d'être très organisés dans le code pour ne pas se perdre étant donné la verbosité imposée par les templates.
La TMP est parfois comparée au langage fonctionnel Haskell, et la librairie Boost.Hana (qui est la librairie de TMP la plus récente et la plus complète) recopie les structures du Haskell et redirige vers la documentation Typeclassopedia (écrite pour le Haskell).
Substitution Failure Is Not An Error
Substitution Failure Is Not An Error (SFINAE) désigne le fait que des paramètres template déduits incorrects ne déclenchent pas une erreur de compilation, si une autre classe / type / valeur / fonction template correspond aux arguments (trouvée par pattern matching). Exemple :
std::void_t
est alors utilisé comme un alias de void
pour pouvoir spécialiser une classe ou une valeur template, si des arguments template dépendant sont corrects :
std::declval<T>()
est une fonction qui est utilisée dans des tests de types (souvent avec decltype
). C'est une fonction qui n'a pas de définition.
std::enable_if_t
est un alias d'une classe donnée (void
par défaut) qui est incorrect si son paramètre booléen template s'évalue à false
. C'est utilisé pour supprimer des templates selon des conditions connues à la compilation :
std::enable_if
peut également contenir une expression incorrecte, auquel cas on le même résultat qu'avec Enabled == false
.
Un pattern est souvent utilisé pour tester la validité d'une expression, et sera implémenté avec std::is_detected_v
en C++20 : il indique à la compilation si le type template est valide avec les arguments passés.
Le mécanisme SFINAE induit un surcoût lors de la compilation. En cas de ralentissement notable, on préfèrera donc si possible d'autres méthodes (comme le tag dispatch).
Fold expressions
Les variadic templates doivent normalement être traités avec de la récursion. Cependant, cela peut demander de la redondance dans le code, et les fonctions imbriquées peuvent gêner le debugging.
Depuis C++17, les fold expressions permettent d'appliquer une récursion de manière automatique, en utilisant un des opérateurs binaires autorisés (il peut s'agir d'une redéfinition d'opérateur).
Dans l'exemple suivant, on utilise std::index_sequence<Is...>
pour passer les entiers de 0
à size(tuple) - 1
, afin d'accéder aux éléments du tuple avec std::get<I>
. std::make_index_sequence<N>
est un alias pour std::index_sequence<0, 1, ..., N-1>
.
Pour aller plus loin
D'autres outils de la librairie standard peuvent être utiles pour simplifier la TMP, comme std::conditionnal_t<Bool, T1, T2>
qui alias T1
ou T2
selon la valeur de Bool
.
Apprendre un langage fonctionnel comme le Haskell permet de mieux comprendre certaines librairies et d'améliorer ses compétences en TMP.
Les expression template permettent d'optimiser des calculs en rendant leur évaluation paresseuse, et en évitant notamment la création d'objets temporaires.
C++20 (ou C++23) apportera la réflexion à la compilation, ce qui permettra d'analyser et de modifier des types durant la compilation. Cela permettra de simplifier beaucoup de tests effectués via SFINAE et apportera de nouvelles fonctionnalités utilisables à la compilation.
Challenges
(**) Implémenter les booléensis_iterator<T>
et is_iterable<T>
. Exemple :
(*) Créer la fonction template nth_times
qui exécute un function object N fois. Aucune boucle ne doit être utilisée : nth_times<200>(f)
va donc copier coller l'appel à f 200 fois.
Les compilateurs savent dérouler une boucle for classique quand ses paramètres sont connus à la compilation. nth_times
sert donc ici uniquement d'exercice.
Last updated