
Zusammenfassung der vorherigen Teile
Aufgrund von Einschränkungen bei der Verwendung von C ++ 11-Compilern und mangelnder Alternativen wollte boost seine eigene Implementierung der Standard-C ++ 11-Bibliothek über die mit dem Compiler gelieferte C ++ 98 / C ++ 03-Bibliothek schreiben.
Static_assert ,
noexcept ,
countof wurden implementiert, und nach Berücksichtigung aller nicht standardmäßigen
Definitionen und Compilerfunktionen wurden Informationen zu den vom aktuellen Compiler unterstützten Funktionen
angezeigt . Eine eigene Implementierung von
nullptr ist enthalten , die bei der Kompilierung ausgewählt wird.
Es ist Zeit für
type_traits und all diese "besondere Vorlagenmagie". Im ersten Teil haben wir meine Implementierung der einfachsten Vorlagen der Standardbibliothek untersucht, aber jetzt werden wir uns eingehender mit den Vorlagen befassen.
Link zu GitHub mit dem Ergebnis für heute für ungeduldige und Nichtleser:
Engagements und konstruktive Kritik sind willkommen
Fortsetzung des Eintauchens in die Welt der "Template Magic" C ++.
Inhaltsverzeichnis
EinführungKapitel 1. Viam Supervadet VadensKapitel 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endifKapitel 3. Finden der perfekten nullptr-ImplementierungKapitel 4. C ++ Template Magic....
4.1 Wir fangen klein an....
4.2 Über wie viele wundersame Fehler das Protokoll für uns kompiliert....
4.3 Zeiger und alles in allem....
4.4 Was wird sonst noch für die Vorlagenbibliothek benötigt?Kapitel 5
...
Kapitel 4. Vorlage "magic" C ++. Fortsetzung
4.2 Über wie viele wundersame Fehler das Protokoll kompiliert
Im
ersten Teil dieses Kapitels wurden die grundlegenden Vorlagen für
type_traits vorgestellt, aber einige weitere fehlten für den gesamten Satz.
Zum Beispiel wurden einfach die Vorlagen
is_integral und
is_floating_point benötigt, die eigentlich sehr trivial definiert sind - durch die Vorlagenspezialisierung für jeden eingebauten Typ. Die Frage stellte sich hier nur bei den "großen" Arten von
Long Long . Tatsache ist, dass dieser integrierte Typ im C ++ - Sprachstandard erst ab Version 11 angezeigt wird. Und es wäre logisch anzunehmen, dass es darauf ankommt, die Version des C ++ - Standards zu überprüfen (die
ohnehin einzigartig schwer zu bestimmen ist ), aber sie war nicht vorhanden.

Denn seit 1999 gibt es den Sprachstandard C99 C, in dem die Typen
long long int und
unsigned long long int bereits vorhanden sind (seit 1999!), Und seit die C ++ - Sprache versucht hat, die Abwärtskompatibilität mit reinem C aufrechtzuerhalten, gibt es viele Compiler (die normalerweise gemischtes C / C ++) hat es gerade als grundlegenden Typ hinzugefügt, noch bevor der C ++ 03-Standard veröffentlicht wurde. Das heißt, die Situation war, dass der eingebaute Typ tatsächlich (von C) ist, aber er ist nicht im C ++ - Standard beschrieben und sollte nicht vorhanden sein. Und das bringt etwas mehr Verwirrung in die Implementierung der Standardbibliothek. Aber schauen wir uns den Code an:
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> { };
Mit dem obigen Code ist alles klar - wir spezialisieren die Vorlage auf die erforderlichen Gleitkommatypen und sagen nach dem „Löschen“ der Typmodifikatoren „Ja“ oder „Nein“ zu dem an uns übergebenen Typ. Als nächstes folgen ganzzahlige Typen:
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> { };
Hier muss man ein wenig innehalten und nachdenken. Für "alte" Ganzzahltypen wie
int ,
bool usw. Wir machen die gleichen Spezialisierungen wie mit
is_floating_point . Für die "neuen" Typen
long long int und ihr vorzeichenloses Gegenstück definieren wir Überladungen nur, wenn es eine LLONG_MAX-
Definition gibt , die in C ++ 11 definiert wurde (als erster C ++ - Standard, der mit C99 kompatibel ist) und in der Header-Datei für die
Klimazonen als Maximum definiert werden sollte eine große Zahl, die in ein Objekt vom Typ
long long int passt.
Climits hat auch einige weitere Makrodefinitionen (für die kleinstmögliche Anzahl und vorzeichenlose Äquivalente), aber ich habe mich für dieses Makro entschieden, was nicht wichtig ist. Wichtig ist, dass im Gegensatz zu boost in dieser Implementierung die "großen" Typen von C nicht als ganzzahlige Konstanten definiert werden, obwohl sie (möglicherweise) im Compiler vorhanden sind. Was wichtiger ist, sind die Typen
char16_t und
char32_t , die ebenfalls in C ++ 11 eingeführt wurden, aber nicht bereits in C99 ausgeliefert wurden (sie erschienen bereits gleichzeitig mit C ++ im C11-Standard), und daher kann ihre Definition in den alten Standards
Seien Sie nur über einen Typ-Alias (zum Beispiel
typedef short char16_t , aber dazu später mehr). In diesem
Fall ist eine weitere Ebene mit
Details zur Vorlagenspezialisierung
erforderlich: _ is_integral , damit die Vorlagenspezialisierung Situationen korrekt
handhabt , in denen diese Typen getrennt (integriert) und durch
typedef definiert sind.
Eine interessante Tatsache ist, dass in einigen alten Compilern diese C-schüchternen "großen" Typen keine integrale Konstante sind . Was kann verstanden und sogar vergeben werden, da diese Typen für C ++ bis zu 11 Standards nicht Standard sind und im Allgemeinen nicht vorhanden sein sollten. Schwer zu verstehen ist jedoch, dass diese Typen im neuesten C ++ - Compiler der Embarcadero-Kreativität (Embarcadero C ++ Builder), den C ++ 11 angeblich unterstützt, in ihren 32-Bit-Assemblys (wie vor 20 Jahren) immer noch keine integrale Konstante sind dann war es Borland noch wahr). Anscheinend fehlt aus diesem Grund der größte Teil der Standard-C ++ 11-Bibliothek in diesen 32-Bit-Assemblys (# include-Verhältnis? Chrono? Wird kosten). Embarcadero scheint beschlossen zu haben, die 64-Bit-Ära mit dem Motto zu erzwingen: „Möchten Sie C ++ 11 oder einen neueren Standard? Erstellen Sie ein 64-Bit-Programm (und nur Clang, unser Compiler kann nicht)! ”
Nachdem wir das Verfahren mit den grundlegenden Sprachtypen abgeschlossen haben, führen wir einige einfachere Muster ein:
Einfache Muster 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
Nur die Tatsache, dass sich Vorlagen auf alle Modifikatoren des Typs spezialisieren (z. B.
flüchtig und
const flüchtig ), ist hier bemerkenswert, weil Einige Compiler neigen dazu, einen der Modifikatoren zu verlieren, wenn sie die Vorlage erweitern.
Separat hebe ich die Implementierung von
is_signed und
is_unsigned hervor :
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> {
Bei der Implementierung dieses Teils trat ich in einen ungleichen Kampf mit Borland C ++ Builder 6.0 ein, der diese beiden Vorlagen nicht zu Erben von
Integral_Constant machen wollte , was schließlich dazu führte, dass Dutzende interner Compilerfehler das
Integral_Konstanten- Verhalten für diese Vorlagen „imitierten“. Hier lohnt es sich vielleicht, noch zu kämpfen und eine knifflige Ableitung des Typs
is_ * un * signiert: Integral_Konstante durch Vorlagen zu finden, aber bisher habe ich diese Aufgabe als keine Priorität verschoben. Interessant im obigen Codeabschnitt ist, wie beim Kompilieren festgestellt wird, dass der Typ nicht signiert / signiert ist. Zunächst werden alle nicht ganzzahligen Typen
markiert, und für sie wird die Vorlage mit dem Vorlagenargument
false an einen separaten spezialisierten Zweig
_sign_unsign_chooser gesendet , der wiederum für alle Typen außer Standard-Gleitkommatypen immer den
Wert == false zurückgibt (sie sind aus offensichtlichen Gründen immer signiert). also wird
_signed :: value wahr sein ). Für ganzzahlige Typen werden einfache, aber unterhaltsame Überprüfungen durchgeführt. Hier verwenden wir die Tatsache, dass bei vorzeichenlosen Ganzzahltypen, wenn die Zahl abnimmt und dann ein Minimum durchläuft (offensichtlich 0), ein Überlauf auftritt und die Zahl ihren maximal möglichen Wert erhält.
Diese Tatsache ist bekannt, ebenso wie die Tatsache, dass ein Überlauf für signierte Typen
ein undefiniertes Verhalten ist und Sie darauf
achten müssen (gemäß dem Standard können Sie eine
int- Variable nicht kleiner als
INT_MIN reduzieren und hoffen, dass Sie aufgrund des Überlaufs
INT_MAX erhalten , nicht 42 oder eine formatierte Festplatte )
Wir schreiben
_Tp (-1) <_Tp (0) , um anhand dieser Tatsache nach dem Typ "Vorzeichen" zu
suchen , und dann nach vorzeichenlosen Typen -1 "Transformationen" durch Überlauf auf die maximale Anzahl dieses Typs, während bei vorzeichenbehafteten Typen ein solcher Vergleich ohne Überlauf durchgeführt wird. und -1 werden mit 0 verglichen.
Und der letzte für heute, aber weit entfernt vom letzten "Trick" in meiner Bibliothek ist die Implementierung von
align_of :
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 hat sich hier erneut durch sein Visual Studio hervorgetan, das selbst mit einem eingebauten nicht standardmäßigen __alignof eingebauten Makro immer noch falsche Ergebnisse liefert, wenn es verwendet wird.
Erklärung von BoostBenutzer von Visual C ++ sollten beachten, dass MSVC unterschiedliche Definitionen von "Ausrichtung" hat. Betrachten Sie beispielsweise den folgenden Code:
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);
In diesem Code schlägt die endgültige Bestätigung für einen 32-Bit-Build fehl, obwohl a1 nicht an einer 8-Byte-Grenze ausgerichtet ist, obwohl boost :: align_of <align_t> meldet, dass align_t eine 8-Byte-Ausrichtung aufweist. Beachten Sie, dass wir immer noch das gleiche Ergebnis erhalten würden, wenn wir anstelle von boost :: align_of das MSVC intrinsic __alignof verwendet hätten. Tatsächlich gelten die Anforderungen (und Versprechen) für die MSVC-Ausrichtung nur für den dynamischen Speicher und nicht für den Stapel.
Ich möchte Sie daran erinnern, was die Vorlage
std :: align_of tun soll.
Geben Sie einen Wert zurück, der die Anforderungen für die Platzierung eines Elements dieses Typs im Speicher darstellt. Ein wenig Ablenkung, dann hat ein Element jedes Typs eine Art Speicherzuordnung, und wenn es für ein Array von Elementen kontinuierlich ist, können Klassen beispielsweise durchaus "Löcher" zwischen den Elementelementen der Klasse haben (
sizeof class
struct { char a;} wird höchstwahrscheinlich nicht gleich 1 sein, obwohl 1 Byte von allem darin enthalten ist, da der Compiler es während des Optimierungsprozesses auf 1 + 3 Bytes ausrichtet.
Schauen wir uns jetzt den Code noch einmal an. Wir deklarieren die
_alignment_of_trick- Struktur, in der wir ein Element des zu
prüfenden Typs mit einem Einzug im Speicher von 1 Byte platzieren. Überprüfen Sie die Ausrichtung, indem Sie einfach die Größe des zu überprüfenden Typs von der Größe der resultierenden Struktur subtrahieren. Das heißt, wenn der Compiler beschließt, ein Leerzeichen zwischen dem Element des zu überprüfenden Typs und dem vorherigen
Zeichen zu "kleben", erhalten wir den Typausrichtungswert in der Struktur.
Auch hier wird zunächst statische Zusicherung als Typ angetroffen. Sie werden deklariert als:
namespace intern {
Tatsächlich werden diese speziellen Vorlagen benötigt, um den
static_assert aus C ++ 11 zu ersetzen, der sich in der Klassendefinition befindet. Solche Assert sind leichter und hochspezialisierter als die allgemeine Implementierung von
STATIC_ASSERT aus
Kapitel 2 und ermöglichen es Ihnen, die Header-Datei
core.h nicht in
type_traits zu
ziehen .

Viele Muster? Es wird mehr geben! Wir werden vorerst darauf eingehen, da die faszinierende Geschichte über die Kombination von Vorlagenprogrammierung mit SFINAE-Technologie sowie darüber, warum ich einen kleinen Codegenerator schreiben musste, weitergehen wird.
Danke für die Aufmerksamkeit.