
Résumé des parties précédentes
En raison de restrictions sur la capacité à utiliser les compilateurs C ++ 11 et du manque d'alternativité, boost a voulu écrire sa propre implémentation de la bibliothèque C ++ 11 standard en plus de la bibliothèque C ++ 98 / C ++ 03 fournie avec le compilateur.
Static_assert ,
noexcept ,
countof ont été implémentés et, après avoir pris en compte toutes les définitions non standard et les fonctionnalités du compilateur, des informations sont apparues sur la fonctionnalité prise en charge par le compilateur actuel. Sa propre implémentation de
nullptr est incluse , qui est sélectionnée au stade de la compilation.
Le temps est venu pour
type_traits et tout ce "modèle magique spécial".
Lien vers GitHub avec le résultat d'aujourd'hui pour les impatients et les non-lecteurs:
Les engagements et les critiques constructives sont les bienvenus
Plongez-vous dans le monde du C ++ "template magic".
Table des matières
PrésentationChapitre 1. Viam supervadet vadensChapitre 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endifChapitre 3. Recherche de l'implémentation nullptr parfaiteChapitre 4. Magie des modèles C ++....
4.1 On commence petit....
4.2 À propos du nombre d'erreurs miraculeuses que le journal compile pour nous....
4.3 Pointeurs et tout-tout....
4.4 Quoi d'autre est nécessaire pour la bibliothèque de modèlesChapitre 5
...
Chapitre 4. Magie des modèles C ++
Après avoir terminé avec les mots clés C ++ 11 et tous les "commutateurs" dépendants de la définition entre leurs implémentations, j'ai commencé à remplir
type_traits . En vérité, j'avais déjà quelques classes de modèles, similaires aux classes standard, qui avaient déjà travaillé dans des projets pendant longtemps et il restait donc à mettre tout cela sous la même forme, ainsi qu'à ajouter les fonctionnalités manquantes.

Honnêtement, je suis inspiré par la programmation de modèles. En particulier, la réalisation que tout cela est une variété d'options: calculs, branchement de code, conditions, vérification des erreurs est effectuée pendant le processus de compilation et rien ne coûte le programme final au moment de l'exécution. Et comme les modèles en C ++ sont essentiellement un
langage de programmation complet de Turing , je m'attendais à ce qu'il soit possible d'implémenter avec élégance et relativement facilement la partie de la norme associée à la programmation sur les modèles. Mais, afin de détruire immédiatement toutes les illusions, je dirai que toute la théorie de l'exhaustivité de Turing est divisée en implémentations concrètes de modèles dans les compilateurs. Et cette partie de l'écriture de la bibliothèque, au lieu de solutions élégantes et de «trucs» de programmation de modèles, s'est transformée en une lutte acharnée avec les compilateurs, alors que chacun s'est «effondré» à sa manière, et c'est bien s'il est entré dans une erreur de compilation interne ou même s'est écrasé étroitement avec exceptions non gérées. GCC (g ++) s'est montré le meilleur de tous, qui «stoppait» stoïquement toutes les constructions de modèles et ne maudissait (dans le cas) que dans les endroits où il y avait un manque de nom de type explicite.
4.1 Commencer petit
J'ai commencé avec des modèles simples pour
std :: integrale_constant ,
std :: bool_constant et des petits modèles similaires.
template<class _Tp, _Tp Val> struct integral_constant {
Sur la base du
conditionnel, vous pouvez entrer des modèles pratiques pour les opérations logiques {"et", "ou", "pas"} sur les types (Et toutes ces opérations sont considérées dès la compilation! C'est génial, n'est-ce pas?):
namespace detail { struct void_type {};
Trois points méritent ici notre attention:
1) Il est important de toujours mettre un espace entre les crochets ('<' et '>') des modèles, car avant C ++ 11, il n'y avait aucune clarification dans la norme sur la façon d'interpréter '>>' et '<<' dans un code comme _ou _ <_ B2, _ou _ <_ B3, _B4 >> , et donc presque tous les compilateurs ont traité cela comme un opérateur de décalage de bit, ce qui conduit à une erreur de compilation.
2) Dans certains compilateurs (Visual Studio 6.0 par exemple), il y avait un bug qui consistait dans le fait qu'il était impossible d'utiliser le type void comme paramètre de modèle. À ces fins, un type void_type distinct est introduit dans le passage ci-dessus pour remplacer le type void où la valeur de paramètre de modèle par défaut est requise.
3) Les très anciens compilateurs (Borland C ++ Builder par exemple) avaient un type booléen implémenté de manière tordue , qui dans certaines situations se transformait «soudainement» en int ( vrai -> 1, faux -> 0), ainsi que des types de variables statiques constantes du type bool (et pas seulement eux), s'ils étaient contenus dans des classes de modèles. En raison de tout ce gâchis, par conséquent, pour une comparaison complètement inoffensive dans le style de my_template_type :: static_bool_value == false, le compilateur pourrait facilement émettre un enchantement ne peut pas convertir un `` type non défini '' en int (0) ou quelque chose comme ça. Par conséquent, il est nécessaire de toujours essayer d'indiquer explicitement le type de valeurs à comparer, aidant ainsi le compilateur à déterminer les types avec lesquels il traite.
Ajoutez plus de travail avec
les valeurs
const et
volatiles . Tout d'abord, le
remove_ ... trivialement implémenté où nous spécialisons simplement le modèle pour certains modificateurs de type - dans le cas où le type est livré avec un modificateur, le compilateur doit, après avoir examiné toutes les spécialisations (rappeler le principe SFINAE du
chapitre précédent ) du modèle, sélectionner le plus approprié (avec indication explicite du modificateur souhaité) :
template<class _Tp> struct is_function; template<class _Tp> struct remove_const {
Et puis nous implémentons des modèles
add_ ... où tout est déjà un peu plus compliqué:
namespace detail { template<class _Tp, bool _IsFunction> struct _add_const_helper { typedef _Tp const type; }; template<class _Tp> struct _add_const_helper<_Tp, true> { typedef _Tp type; }; template<class _Tp, bool _IsFunction> struct _add_volatile_helper { typedef _Tp volatile type; }; template<class _Tp> struct _add_volatile_helper<_Tp, true> { typedef _Tp type; }; template<class _Tp, bool _IsFunction> struct _add_cv_helper { typedef _Tp const volatile type; }; template<class _Tp> struct _add_cv_helper<_Tp, true> { typedef _Tp type; }; }
Ici, nous traitons soigneusement les types de référence séparément afin de ne pas perdre le lien. De plus, nous n'oublierons pas les types de fonctions qu'il est impossible de rendre
volatiles ou
const en principe, donc nous les laisserons «tels quels». Je peux dire que tout cela semble très simple, mais c'est exactement le cas lorsque "le diable est dans les détails", ou plutôt, "les bogues sont dans les détails de l'implémentation".
La fin de la première partie du quatrième chapitre. Dans la
deuxième partie, je parlerai de la façon dont la programmation des modèles est donnée au compilateur, et il y aura également plus de magie de modèles cool. Ah, et pourtant - pourquoi est
long long pas une
constante intégrale, selon certains compilateurs à ce jour.
Merci de votre attention.