Wie habe ich die Standard-C ++ 11-Bibliothek geschrieben oder warum ist Boost so beängstigend? Kapitel 4.1

Wir setzen das Abenteuer fort.

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".

Link zu GitHub mit dem Ergebnis für heute für ungeduldige und Nichtleser:

Engagements und konstruktive Kritik sind willkommen

Tauchen Sie ein in die Welt von "Template Magic" C ++.

Inhaltsverzeichnis


Einführung
Kapitel 1. Viam Supervadet Vadens
Kapitel 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
Kapitel 3. Finden der perfekten nullptr-Implementierung
Kapitel 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. C ++ Template Magic


Nachdem ich mit den C ++ 11-Schlüsselwörtern und allen definierungsabhängigen "Schaltern" zwischen ihren Implementierungen fertig war , begann ich, type_traits zu füllen. In Wahrheit hatte ich bereits einige Vorlagenklassen, ähnlich den Standardklassen, die bereits lange in Projekten gearbeitet hatten, und daher blieb es, all dies in die gleiche Form zu bringen und die fehlenden Funktionen hinzuzufügen.

Bild Ehrlich gesagt bin ich von der Vorlagenprogrammierung inspiriert. Insbesondere die Erkenntnis, dass dies alles eine Vielzahl von Optionen ist: Berechnungen, Code-Verzweigung, Bedingungen, Fehlerprüfung werden während des Kompilierungsprozesses durchgeführt und nichts kostet das endgültige Programm zur Laufzeit. Und da Vorlagen in C ++ im Wesentlichen eine Turing-vollständige Programmiersprache sind , war ich gespannt, wie elegant und relativ einfach es möglich sein würde, den Teil des Standards zu implementieren, der mit der Programmierung auf Vorlagen verbunden ist. Um jedoch alle Illusionen sofort zu zerstören, werde ich sagen, dass die gesamte Theorie der Turing-Vollständigkeit in konkrete Implementierungen von Vorlagen in Compilern zerlegt ist. Und dieser Teil des Schreibens der Bibliothek wurde anstelle eleganter Lösungen und „Tricks“ der Vorlagenprogrammierung zu einem heftigen Kampf mit Compilern, während jeder auf seine Weise „zusammenbrach“, und es ist gut, wenn er in einen verwischten internen Compilerfehler geriet oder sogar eng zusammenbrach unbehandelte Ausnahmen. GCC (g ++) zeigte sich am besten, das alle Template-Konstruktionen stoisch „kaute“ und (im Fall) nur an Stellen verfluchte, an denen ein expliziter Typname fehlte .

4.1 Klein anfangen


Ich habe mit einfachen Vorlagen für std :: Integral_constant , std :: bool_constant und ähnlichen kleinen Vorlagen begonnen.

template<class _Tp, _Tp Val> struct integral_constant { // convenient template for integral constant types static const _Tp value = Val; typedef const _Tp value_type; typedef integral_constant<_Tp, Val> type; operator value_type() const { // return stored value return (value); } value_type operator()() const { // return stored value return (value); } }; typedef integral_constant<bool, true> true_type; typedef integral_constant<bool, false> false_type; template<bool Val> struct bool_constant : public integral_constant<bool, Val> {}; // Primary template. // Define a member typedef @c type to one of two argument types. template<bool _Cond, class _Iftrue, class _Iffalse> struct conditional { typedef _Iftrue type; }; // Partial specialization for false. template<class _Iftrue, class _Iffalse> struct conditional<false, _Iftrue, _Iffalse> { typedef _Iffalse type; }; 

Basierend auf Bedingungen können Sie praktische Vorlagen für logische Operationen {"und", "oder", "nicht"} über Typen eingeben (und all diese Operationen werden bereits in der Kompilierungsphase berücksichtigt! Es ist großartig, nicht wahr?):

 namespace detail { struct void_type {}; //typedef void void_type; template<class _B1 = void_type, class _B2 = void_type, class _B3 = void_type, class _B4 = void_type> struct _or_ : public conditional<_B1::value, _B1, _or_<_B2, _or_<_B3, _B4> > >::type { }; template<> struct _or_<void_type, void_type, void_type, void_type>; template<class _B1> struct _or_<_B1, void_type, void_type, void_type> : public _B1 { }; template<class _B1, class _B2> struct _or_<_B1, _B2, void_type, void_type> : public conditional<_B1::value, _B1, _B2>::type { }; template<class _B1, class _B2, class _B3> struct _or_<_B1, _B2, _B3, void_type> : public conditional<_B1::value, _B1, _or_<_B2, _B3> >::type { }; template<class _B1 = void_type, class _B2 = void_type, class _B3 = void_type, class _B4 = void_type> struct _and_; template<> struct _and_<void_type, void_type, void_type, void_type>; template<class _B1> struct _and_<_B1, void_type, void_type, void_type> : public _B1 { }; template<class _B1, class _B2> struct _and_<_B1, _B2, void_type, void_type> : public conditional<_B1::value, _B2, _B1>::type { }; template<class _B1, class _B2, class _B3> struct _and_<_B1, _B2, _B3, void_type> : public conditional<_B1::value, _and_<_B2, _B3>, _B1>::type { }; template<class _Pp> struct _not_ { static const bool value = !bool(_Pp::value); typedef const bool value_type; typedef integral_constant<bool, _not_::value == bool(true)> type; operator value_type() const { // return stored value return (value); } value_type operator()() const { // return stored value return (value); } }; } 

Drei Punkte verdienen hier Aufmerksamkeit:

1) Es ist wichtig, immer ein Leerzeichen zwischen die spitzen Klammern ('<' und '>') der Vorlagen zu setzen, da vor C ++ 11 im Standard keine Klarstellung darüber enthalten war, wie '>>' und '<<' in Code wie _oder _ <_ B2 zu interpretieren sind. _oder _ <_ B3, _B4 >> , und daher haben fast alle Compiler dies als Bitverschiebungsoperator behandelt, was zu einem Kompilierungsfehler führt.

2) In einigen Compilern (z. B. Visual Studio 6.0) gab es einen Fehler, der darin bestand, dass der void- Typ nicht als Vorlagenparameter verwendet werden konnte. Zu diesem Zweck wird im obigen Abschnitt ein separater void_type- Typ eingeführt, um den void- Typ zu ersetzen, für den der Standardwert des Vorlagenparameters erforderlich ist.

3) Sehr alte Compiler (zum Beispiel Borland C ++ Builder) hatten einen schief implementierten Typ- Bool , der in einigen Situationen „plötzlich“ zu int ( true -> 1, false -> 0) wurde, sowie Typen konstanter statischer Variablen des Typs bool (und nicht nur sie), wenn sie in Vorlagenklassen enthalten waren. Aufgrund all dieser Unordnung könnte der Compiler für einen völlig harmlosen Vergleich im Stil von my_template_type :: static_bool_value == false leicht eine Verzauberung ausgeben , die nicht 'undefined type' in int (0) oder ähnliches umwandeln kann . Daher muss immer versucht werden, den Typ der zu vergleichenden Werte explizit anzugeben, damit der Compiler bestimmen kann, um welche Typen es sich handelt.


Fügen Sie mehr Arbeit mit konstanten und flüchtigen Werten hinzu. Zunächst das trivial implementierte remove_ ..., bei dem wir die Vorlage einfach auf bestimmte Typmodifikatoren spezialisieren. Wenn der Typ mit dem Modifikator in die Vorlage eingeht, muss der Compiler nach Prüfung aller Spezialisierungen (beachten Sie das SFINAE-Prinzip aus dem vorherigen Kapitel ) der Vorlage die am besten geeignete auswählen (mit expliziter Angabe des gewünschten Modifikators). ::

 template<class _Tp> struct is_function; template<class _Tp> struct remove_const { // remove top level const qualifier typedef _Tp type; }; template<class _Tp> struct remove_const<const _Tp> { // remove top level const qualifier typedef _Tp type; }; template<class _Tp> struct remove_const<const volatile _Tp> { // remove top level const qualifier typedef volatile _Tp type; }; // remove_volatile template<class _Tp> struct remove_volatile { // remove top level volatile qualifier typedef _Tp type; }; template<class _Tp> struct remove_volatile<volatile _Tp> { // remove top level volatile qualifier typedef _Tp type; }; // remove_cv template<class _Tp> struct remove_cv { // remove top level const and volatile qualifiers typedef typename remove_const<typename remove_volatile<_Tp>::type>::type type; }; 

Und dann implementieren wir add_- Vorlagen ... wo alles schon etwas komplizierter ist:

 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; }; } // add_const template<class _Tp> struct add_const: public detail::_add_const_helper<_Tp, is_function<_Tp>::value> { }; template<class _Tp> struct add_const<_Tp&> { typedef _Tp & type; }; // add_volatile template<class _Tp> struct add_volatile : public detail::_add_volatile_helper<_Tp, is_function<_Tp>::value> { }; template<class _Tp> struct add_volatile<_Tp&> { typedef _Tp & type; }; // add_cv template<class _Tp> struct add_cv : public detail::_add_cv_helper<_Tp, is_function<_Tp>::value> { }; template<class _Tp> struct add_cv<_Tp&> { typedef _Tp & type; }; 

Hier verarbeiten wir die Referenztypen sorgfältig separat, um den Link nicht zu verlieren. Wir werden auch nicht vergessen, welche Arten von Funktionen es im Prinzip unmöglich macht, flüchtig oder konstant zu machen, deshalb werden wir sie "wie sie sind" belassen . Ich kann sagen, dass dies alles sehr einfach aussieht, aber genau dies ist der Fall, wenn "der Teufel im Detail steckt" oder vielmehr "Fehler im Detail der Implementierung".

Das Ende des ersten Teils des vierten Kapitels. Im zweiten Teil werde ich darüber sprechen, wie hart die Vorlagenprogrammierung dem Compiler gegeben wird, und es wird auch mehr coole Vorlagenmagie geben. Ah, und doch - warum ist Long Long laut einigen Compilern bis heute keine integrale Konstante ?

Danke für die Aufmerksamkeit.

Source: https://habr.com/ru/post/de417547/


All Articles