
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". Dans la première partie, nous avons examiné mon implémentation des modèles les plus simples de la bibliothèque standard, mais nous allons maintenant approfondir les modèles.
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
Poursuite de l'immersion dans le monde du "template magic" C ++.
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. Modèle C ++ "magique". Continuation
4.2 À propos du nombre d'erreurs miraculeuses que le journal compile
Dans la
première partie de ce chapitre, les modèles de base
type_traits ont été introduits, mais quelques autres manquaient pour l'ensemble complet.
Par exemple, les
modèles is_integral et
is_floating_point étaient simplement nécessaires, qui sont en fait très trivialement définis - grâce à la spécialisation du modèle pour chaque type intégré. La question ne se pose ici qu'avec les "grands" types de
long long . Le fait est que ce type intégré n'apparaît dans la norme de langage C ++ qu'à partir de la version 11. Et il serait logique de supposer que tout se résume à vérifier la version de la norme C ++ (ce qui
est particulièrement difficile à déterminer de toute façon ), mais ce n'était pas là.

Parce que, depuis 1999, la norme du langage C99 C existe dans laquelle les types
long long int et
unsigned long long int sont déjà présents (depuis 1999!), Et comme le langage C ++ a cherché à maintenir la compatibilité descendante avec le C pur, de nombreux compilateurs (qui généralement mixte C / C ++) l'a juste ajouté comme type fondamental avant même la publication de la norme C ++ 03. Autrement dit, la situation était que le type intégré est en fait (de C), mais il n'est pas décrit dans la norme C ++ et ne devrait pas être là. Et cela ajoute un peu plus de confusion à l'implémentation de la bibliothèque standard. Mais regardons le code:
namespace detail { template <class> struct _is_floating_point : public false_type {}; template<> struct _is_floating_point<float> : public true_type {}; template<> struct _is_floating_point<double> : public true_type {}; template<> struct _is_floating_point<long double> : public true_type {}; } template <class _Tp> struct is_floating_point : public detail::_is_floating_point<typename remove_cv<_Tp>::type> { };
Tout est clair avec le code ci-dessus - nous spécialisons le modèle pour les types à virgule flottante nécessaires, et, après avoir «effacé» les modificateurs de type, nous disons «oui» ou «non» au type qui nous est transmis. Viennent ensuite les types entiers:
namespace detail { template <class> struct _is_integral_impl : public false_type {}; template<> struct _is_integral_impl<bool> : public true_type {}; template<> struct _is_integral_impl<char> : public true_type {}; template<> struct _is_integral_impl<wchar_t> : public true_type {}; template<> struct _is_integral_impl<unsigned char> : public true_type {}; template<> struct _is_integral_impl<unsigned short int> : public true_type {}; template<> struct _is_integral_impl<unsigned int> : public true_type {}; template<> struct _is_integral_impl<unsigned long int> : public true_type {}; #ifdef LLONG_MAX template<> struct _is_integral_impl<unsigned long long int> : public true_type {}; #endif template<> struct _is_integral_impl<signed char> : public true_type {}; template<> struct _is_integral_impl<short int> : public true_type {}; template<> struct _is_integral_impl<int> : public true_type {}; template<> struct _is_integral_impl<long int> : public true_type {}; #ifdef LLONG_MAX template<> struct _is_integral_impl<long long int> : public true_type {}; #endif template <class _Tp> struct _is_integral : public _is_integral_impl<_Tp> {}; template<> struct _is_integral<char16_t> : public true_type {}; template<> struct _is_integral<char32_t> : public true_type {}; template<> struct _is_integral<int64_t> : public true_type {}; template<> struct _is_integral<uint64_t> : public true_type {}; } template <class _Tp> struct is_integral : public detail::_is_integral<typename remove_cv<_Tp>::type> { };
Ici, vous devez vous arrêter un peu et réfléchir. Pour les "anciens" types entiers comme
int ,
bool , etc. nous faisons les mêmes spécialisations qu'avec
is_floating_point . Pour les "nouveaux" types
long long int et son homologue non signé, nous définissons les surcharges uniquement s'il existe un LLONG_MAX
define , qui a été défini en C ++ 11 (comme le premier standard C ++ compatible avec C99), et doit être défini dans le fichier d'en-tête
climits comme maximum un grand nombre qui s'inscrit dans un objet de type
long long int .
Climits a également quelques définitions de macro supplémentaires (pour le plus petit nombre possible et les équivalents non signés), mais j'ai décidé d'utiliser cette macro, ce qui n'est pas important. L'important est que, contrairement à boost, dans cette implémentation, les "grands" types de C ne seront pas définis comme des constantes entières, bien qu'ils soient (éventuellement) présents dans le compilateur. Ce qui est plus important, ce sont les types
char16_t et
char32_t , qui ont également été introduits en C ++ 11, mais ils n'étaient pas déjà livrés en C99 (ils apparaissaient déjà simultanément avec C ++ dans la norme C11), et donc, dans les anciennes normes, leur définition peut être uniquement via un alias de type (par exemple
typedef short char16_t , mais plus à ce sujet plus tard). Si tel est le cas, pour que la spécialisation de modèle gère correctement les situations lorsque ces types sont séparés (intégrés) et lorsqu'ils sont définis via
typedef , une couche supplémentaire de
détails de spécialisation de modèle
:: _ is_integral est nécessaire .
Un fait intéressant est que dans certains anciens compilateurs, ces "grands" types C-timides ne sont pas des constantes intégrales . Ce qui peut être compris et même pardonné, car ces types ne sont pas standard pour C ++ jusqu'à 11 normes, et en général ils ne devraient pas être là. Mais ce qui est difficile à comprendre, c'est que ces types dans le dernier compilateur C ++ de la créativité Embarcadero (Embarcadero C ++ Builder), que C ++ 11 est censé prendre en charge, ne sont toujours pas constants dans leurs assemblages 32 bits (comme il y a 20 ans alors c'était Borland toujours vrai). Apparemment à cause de cela, y compris, la plupart de la bibliothèque C ++ 11 standard est manquante dans ces assemblages 32 bits (#include ratio? Chrono? Will cost). Embarcadero semble avoir décidé de forcer l'ère 64 bits avec la devise: «Voulez-vous C ++ 11 ou une norme plus récente? Construisez un programme 64 bits (et seulement clang, notre compilateur ne peut pas)! »
Après avoir terminé la procédure avec les types de langage fondamentaux, nous introduisons quelques modèles plus simples:
Modèles simples template <bool, class _Tp = detail::void_type> struct enable_if { }; template <class _Tp> struct enable_if<true, _Tp> { typedef _Tp type; }; template<class, class> struct is_same : public false_type { }; template<class _Tp> struct is_same<_Tp, _Tp> : public true_type
Seul le fait que les modèles se spécialisent pour tous les modificateurs du type (
volatile et
const volatile par exemple) est à noter ici, car certains compilateurs ont tendance à «perdre» l'un des modificateurs lors de l'expansion du modèle.
Séparément, je souligne l'implémentation de
is_signed et
is_unsigned :
namespace detail { template<bool> struct _sign_unsign_chooser; template<class _Tp> struct _signed_comparer { static const bool value = _Tp(-1) < _Tp(0); }; template<class _Tp> struct _unsigned_comparer { static const bool value = _Tp(0) < _Tp(-1); }; template<bool Val> struct _cat_base : integral_constant<bool, Val> {
Lors de la mise en œuvre de cette partie, je suis entré dans une bataille inégale avec Borland C ++ Builder 6.0, qui ne voulait pas faire de ces deux modèles des héritiers de
integr_constant , ce qui a finalement entraîné des dizaines d'erreur de compilation interne «imitant» le comportement integr_constant de ces modèles. Ici, peut-être, il vaut la peine de se battre et de trouver une sorte de dérivation délicate du type
is_ * un * signé: integr_constant via des modèles, mais jusqu'à présent, j'ai reporté cette tâche comme une priorité. Ce qui est intéressant dans la section de code ci-dessus, c'est comment au moment de la compilation, il est déterminé que le type n'est pas signé / signé. Pour commencer, tous les types non entiers sont
marqués et pour eux, le modèle va à une branche spécialisée distincte
_sign_unsign_chooser avec l'argument de modèle
false , qui à son tour renvoie toujours la
valeur == false pour tous les types, sauf les types à virgule flottante standard (ils sont toujours signés pour des raisons évidentes, donc
_signed :: value sera
vrai ). Pour les types entiers, simples, mais plutôt divertissants, des vérifications sont effectuées. Ici, nous utilisons le fait que pour les types entiers non signés, lorsque le nombre diminue puis passe par un minimum (0 évidemment), un débordement se produit et le nombre acquiert sa valeur maximale possible.
Ce fait est bien connu, ainsi que le fait que pour les types signés, le débordement
est un comportement non défini et que vous devez le surveiller (selon la norme, vous ne pouvez pas réduire une variable
int inférieure à
INT_MIN et espérer qu'à la suite du débordement, vous obtiendrez
INT_MAX , pas 42 ou un disque dur formaté )
Nous écrivons
_Tp (-1) <_Tp (0) pour vérifier le type "signe" en utilisant ce fait, puis pour les types non signés -1 "transforme" par débordement au nombre maximum de ce type, tandis que pour les types signés une telle comparaison sera effectuée sans débordement, et -1 sera comparé à 0.
Et le dernier pour aujourd'hui, mais loin du dernier "truc" de ma bibliothèque est l'implémentation de l'
alignement_de :
namespace detail { template <class _Tp> struct _alignment_of_trick { char c; _Tp t; _alignment_of_trick(); }; template <unsigned A, unsigned S> struct _alignment_logic_helper { static const std::size_t value = A < S ? A : S; }; template <unsigned A> struct _alignment_logic_helper<A, 0> { static const std::size_t value = A; }; template <unsigned S> struct _alignment_logic_helper<0, S> { static const std::size_t value = S; }; template< class _Tp > struct _alignment_of_impl { #if _MSC_VER > 1400
Microsoft a encore une fois excellé ici avec leur Visual Studio, qui, même avec une macro intégrée __alignof non standard intégrée , produit toujours des résultats incorrects lors de son utilisation.
Explication de boostLes utilisateurs de Visual C ++ doivent noter que MSVC a différentes définitions de «alignement». Par exemple, considérez le code suivant:
typedef long long align_t; assert(boost::alignment_of<align_t>::value % 8 == 0); align_t a; assert(((std::uintptr_t)&a % 8) == 0); char c = 0; align_t a1; assert(((std::uintptr_t)&a1 % 8) == 0);
Dans ce code, même si boost :: alignement_al <align_t> signale que align_t a un alignement sur 8 octets, l'assertion finale échouera pour une génération 32 bits car a1 n'est pas aligné sur une limite de 8 octets. Notez que si nous avions utilisé le MSVC intrinsèque __alignof à la place de boost :: alignement_of, nous obtiendrions toujours le même résultat. En fait, les exigences d'alignement MSVC (et les promesses) ne s'appliquent vraiment qu'au stockage dynamique, et non à la pile.
Permettez-moi de vous rappeler ce que le modèle
std :: alignement_of devrait faire - retourner une valeur qui représente les exigences pour le placement d'un élément de ce type en mémoire. Une petite distraction, puis un élément de chaque type a une sorte d'allocation de mémoire, et s'il est continu pour un tableau d'éléments, alors, par exemple, les classes peuvent bien avoir des «trous» entre les éléments membres de la classe (
sizeof class
struct { char a;} ne sera probablement pas égal à 1, bien qu'il y ait 1 octet de tout à l'intérieur, car le compilateur l'alignera sur 1 + 3 octets pendant le processus d'optimisation).
Maintenant, regardons à nouveau le code. Nous déclarons la structure
_alignment_of_trick , dans laquelle nous
plaçons un élément du type en cours de vérification avec un «retrait» en mémoire de 1 octet. Et vérifiez l'alignement en soustrayant simplement la taille du type à vérifier de la taille de la structure résultante. Autrement dit, si le compilateur décide de «coller» un espace vide entre l'élément du type en cours de vérification et le caractère précédent, alors nous obtenons la valeur d'alignement du type dans la structure.
Ici aussi, l'assertion statique est d'abord rencontrée en tant que type. Ils sont déclarés comme:
namespace intern {
En fait, ces modèles spécialisés sont nécessaires pour remplacer le
static_assert de C ++ 11, qui se trouve à l'intérieur de la définition de classe. Ces assertions sont plus légères et hautement spécialisées que l'implémentation générale de
STATIC_ASSERT du
chapitre 2 , et vous permettent de ne pas faire glisser le fichier d'en-tête
core.h dans
type_traits .

Beaucoup de motifs? Il y en aura plus! Nous allons nous attarder sur cela pour l'instant, car l'histoire fascinante de la combinaison de la programmation de modèles avec la technologie SFINAE, ainsi que la raison pour laquelle j'ai dû écrire un petit générateur de code, se poursuivra.
Merci de votre attention.