Wie ich die Standard-C ++ 11-Bibliothek geschrieben habe oder warum der Boost so unheimlich ist. Kapitel 4.3

    Wir setzen das Abenteuer fort.

    Zusammenfassung der vorherigen Teile


    Aufgrund von Einschränkungen auf der Fähigkeit C ++ Compiler 11 und aus gibt es keine Alternative boost'u den Wunsch , eine eigene Implementierung von Standard - C ++ 11 Bibliotheken über mit dem Compiler C ++ Bibliotheken 98 / C ++ geliefert zu schreiben 03.

    Es wurde implementiert static_assert , noexcept , countof sowie nach Berücksichtigung Von allen nicht standardmäßigen Definitionen und Funktionen von Compilern gibt es Informationen über die vom aktuellen Compiler unterstützte Funktionalität. Enthalten ist eine eigene Implementierung von nullptr , die bei der Kompilierung ausgewählt wird.

    Es ist Zeit für die Eingabe von Spurenund all das "besondere gemusterte Magie". In den vorangegangenen Teilen dieses Kapitels haben wir mir meine Implementierung der grundlegenden Vorlagen der Standardbibliothek angesehen. In diesem Teil werden wir die Kombination der SFINAE-Technik mit Vorlagen und ein wenig über die Codegenerierung diskutieren.

    Link zu GitHub mit dem Ergebnis für heute für ungeduldige und Nichtleser:
    Commits und konstruktive Kritik sind willkommen
    Weitere C ++ - Vorlagen unter der Katze.

    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 Sie die perfekte Realisierung nullptr
    Kapitel 4. Gemusterte „magic» die C ++
    .... 4.1 klein anfangen
    .... 4.2 Über wie viele Fehler , die wir Wunderbare Zusammenstellungen werden vom Protokoll vorbereitet
    .... 4.3 Zeiger und Alles-Alles-Alles
    .... 4.4 Was wird noch benötigt für die Beispielbibliothek
    Kapitel 5.
    ...

    Kapitel 4. C ++ - Vorlage "Magic". Fortsetzung


    4.3 Hinweise und alles


    Zu diesem Zeitpunkt musste ich lediglich Informationen darüber abrufen, ob der Typ ein Array für std :: is_array ist, und Sie können mit den Vorlagen für Zeiger fortfahren. Die Umsetzung war auch trivial, aber nicht ohne Annahmen.

    // is_array
    template<class>
    struct is_array :
        public false_type { };
    template<class _Tp, std::size_t _Size>
    struct is_array<_Tp[_Size]> :
        public true_type { };
    /*template<class _Tp>
    struct is_array<_Tp[]>:
        public true_type { }; */
    

    Eine einfache Schablonenspezialisierung für Arrays einer bestimmten Länge "fängt" alle Arten von Arrays, das Problem tritt jedoch beim unvollständigen Typ T [] (einem Array ohne Angabe einer Länge) auf. Tatsache ist, dass dieser Typ von einigen Compilern (C ++ Builder) beim Spezialisieren einer Vorlage nicht definiert wird, und ich habe hier noch keine universelle Lösung gefunden.

    Nachdem die Bibliothek die Definition von eingebauten Typen, die Ausrichtung im Typenspeicher, das Arbeiten mit Typmodifizierern und andere grundlegende Dinge durch Vorlagen während der Kompilierungszeit "gelernt" hat, ist es Zeit für Zeiger und Referenzen.

    BildIn C ++ können Sie zwei Gruppen von Zeigern auswählen - Zeiger auf Klassenmitglieder und Zeiger auf andere Objekte. Warum ist diese Trennung für die weitere Implementierung der Standardbibliothek wichtig? Die Tatsache , dass die Mitglieder der Klasse Zeiger haben einen signifikanten Unterschied von den anderen durch das Vorhandensein von Anzeichen der dieses , das heißt, Zeiger auf ein Objekt dieser Klasse. Gemäß dem Standard haben Zeiger auf ein Member einer Klasse eine eigene Definitionssyntax, sind ein separater Typ und können nicht durch einen regulären Zeiger dargestellt werden. In der Praxis wird dies dadurch ausgedrückt, dass die Größe eines Zeigers auf ein Member einer Klasse normalerweise größer ist als die Größe eines regulären Zeigers (der == sizeof (void *) ), da zur Implementierung von virtuellen Memberfunktionen der Klasse sowie zum Speichern dieses ZeigersCompiler implementieren normalerweise Zeiger auf einen Klassenmitglied als Struktur (Informationen zu virtuellen Funktionen und Struktur ). Die Übergabe von Zeigern an die Mitglieder der Klasse wird gemäß dem Standard im Ermessen des Compilers bleiben, aber wir werden uns diesen Unterschied in Größe und Darstellung merken, wenn weiterer Code in Betracht gezogen wird.

    Um einen regulären Zeiger auf ein Objekt zu definieren, schreiben wir eine einfache is_pointer- Vorlage sowie die is_lvalue_reference- Vorlage für Verweise auf das Objekt ( wir werden die is_rvalue_reference verschieben, da bis zum 11. Standard des Operators && und der gesamten Verschiebesemantik nicht vorhanden waren).

    namespace detail
    {
        template<class>
        struct _is_pointer_helper :
            public false_type { };
        template<class _Tp>
        struct _is_pointer_helper<_Tp*> :
            public true_type { };
    }
    // is_pointer
    template<class _Tp>
    struct is_pointer :
        public detail::_is_pointer_helper<typename remove_cv<_Tp>::type>::type
    { };
    // is_lvalue_reference
    template<class>
    struct is_lvalue_reference :
        public false_type { };
    template<class _Tp>
    struct is_lvalue_reference<_Tp&> :
        public true_type { };
    

    Hier gibt es schon etwas grundlegend Neues, in den vorangegangenen Teilen dieses Kapitels wurde dasselbe getan. Wir setzen die Definitionen von Zeigern auf Objekte fort - jetzt betrachten wir die Zeiger auf Funktionen.
    Es ist wichtig zu verstehen, dass eine Funktion und eine Memberfunktion einer Klasse gemäß dem Standard völlig unterschiedliche Entitäten sind:

    • Der erste Zeiger ist normal (ein Zeiger auf ein Objekt), der zweite ist ein Zeiger auf ein Mitglied der Klasse.

    void (*func_ptr)(int); // указатель 'func_ptr' на функцию вида 'void func(int){}'
    void (ClassType::*mem_func_ptr)(int); // указатель 'mem_func_ptr' на функцию-член класса 'ClassType' вида 'void ClassType::func(int){}'
    

    • Sie können eine Verknüpfung zur ersten Verknüpfung erstellen (Objektverknüpfung), und Sie können keine zweite Verknüpfung erstellen.

    void (&func_ref)(int); // ссылка 'func_ref' на функцию вида 'void func(int){}'
    //-------------------- // ссылка на функцию-член класса не определена стандартом
    
    Ich möchte hier nur etwas zur Codegenerierung erwähnen. Da es vor C ++ 11 keine Vorlagen mit einer variablen Anzahl von Parametern gab, wurden alle Vorlagen, bei denen eine andere Anzahl von Parametern vorhanden sein konnte, durch die Spezialisierung der Hauptvorlage mit einer beliebigen Anzahl von Parametern bestimmtbei der Eingabe und deren Initialisierung durch Standard-Dummy-Parameter. Dasselbe gilt für Funktionsüberlastungen, da Es gab auch keine Makros mit einer variablen Anzahl von Parametern. Da das Schreiben von Händen auf 60-70 Zeilen derselben Typvorlagenspezialisierung, das Überladen von Funktionen eine eher langweilige und nutzlose Übung ist, und außerdem die Möglichkeit, dass ich einen Fehler machen kann, schrieb ich einen einfachen Generator von Codevorlagen und Überladungsfunktionen für diese Zwecke. Ich beschloss, die Definition der Funktionen auf 24 Parameter zu beschränken, und das sieht im Code eher umständlich, aber einfach und klar aus:

    namespace detail
    {
        template <class R>
        struct _is_function_ptr_helper : false_type {};
        template <class R >
        struct _is_function_ptr_helper<R(*)()> : true_type {};
        template <class R >
        struct _is_function_ptr_helper<R(*)(...)> : true_type {};
        template <class R, class T0>
        struct _is_function_ptr_helper<R(*)(T0)> : true_type {};
        template <class R, class T0>
        struct _is_function_ptr_helper<R(*)(T0 ...)> : true_type {};
    

    ...
        template <class R, class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24>
        struct _is_function_ptr_helper<R(*)(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24)> : true_type {};
        template <class R, class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24>
        struct _is_function_ptr_helper<R(*)(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24 ...)> : true_type {};
    }
    

    Wir definieren die Typen, die dem vorherigen Kapitel für die SFINAE-Technik bekannt sind:

    namespace detail
    {
        // SFINAE magic
        typedef char _yes_type;
        struct _no_type
        {
            char padding[8];
        };
    }
    

    Einige weitere Makros für mehr Komfort.
    namespace detail
    {
        #define _IS_MEM_FUN_PTR_CLR \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS)); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...)); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) const); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) const volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) const); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) const volatile);
    #ifdef _STDEX_CDECL
    		_no_type _STDEX_CDECL _is_mem_function_ptr(...);
    #define _IS_MEM_FUN_CDECL_PTR \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS)); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) const); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) const volatile);
    #define _IS_MEM_FUN_STDCALL_PTR \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS)); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) const); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) const volatile);
    #define _IS_MEM_FUN_FASTCALL_PTR \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS)); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) const); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) const volatile);
    #else
    		_no_type _is_mem_function_ptr(...);
    #define _IS_MEM_FUN_CDECL_PTR
    #define _IS_MEM_FUN_STDCALL_PTR
    #define _IS_MEM_FUN_FASTCALL_PTR
    #endif
    #define _IS_MEM_FUN_PTR \
    		_IS_MEM_FUN_PTR_CLR \
    		_IS_MEM_FUN_CDECL_PTR \
    		_IS_MEM_FUN_STDCALL_PTR \
    		_IS_MEM_FUN_FASTCALL_PTR
    }
    


    Makros sind so definiert, dass es relativ bequem ist, TYPES und ARGS-Definitionen als Liste von Typen und Parametern zu überschreiben. Anschließend wird das Makro _IS_MEM_FUN_PTR verwendet, um Definitionen für alle möglichen Funktionstypen durch den Präprozessor zu generieren. Es ist auch zu beachten, dass für die Compiler von Microsoft eine andere Aufrufkonvention wichtig ist ( __ fastcall , __stdcall und __ cdecl ), da Bei unterschiedlichen Konventionen unterscheiden sich die Funktionen, obwohl die Menge der Argumente und der Rückgabewert gleich sind. Daher wird die gesamte grandiose Konstruktion von Makros relativ kompakt verwendet:

    namespace detail
    {
        #define TYPES
        #define ARGS
        _IS_MEM_FUN_PTR
    #undef TYPES
    #undef ARGS
        #define TYPES , class T0
        #define ARGS T0
        _IS_MEM_FUN_PTR
    #undef TYPES
    #undef ARGS
        #define TYPES , class T0, class T1
        #define ARGS T0, T1
        _IS_MEM_FUN_PTR
    #undef TYPES
    #undef ARGS
    

    ...
        #define TYPES , class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24
        #define ARGS T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24
        _IS_MEM_FUN_PTR
    #undef TYPES
    #undef ARGS
    // не забудем убрать все лишние define за собой:
    #undef _IS_MEM_FUN_PTR
    #undef _IS_MEM_FUN_PTR_CLR 		
    #undef _IS_MEM_FUN_CDECL_PTR
    #undef _IS_MEM_FUN_STDCALL_PTR
    #undef _IS_MEM_FUN_FASTCALL_PTR
    }
    

    Und jetzt, um das zu schreiben, was alles geschrieben wurde:

    namespace detail
    {
        template <class _Tp, bool _IsRef>
        struct _is_mem_function_ptr_impl
        {
            static _Tp *p;
            static const bool value = (sizeof(_is_mem_function_ptr(_is_mem_function_ptr_impl::p)) == sizeof(_yes_type));
            typedef typename integral_constant<bool, _is_mem_function_ptr_impl::value == bool(true)>::type type;
        };
        template <class _Tp>
        struct _is_mem_function_ptr_impl<_Tp, true>:
            public false_type
        {};
        template <class _Tp>
        struct _is_mem_function_ptr_helper:
            public _is_mem_function_ptr_impl<_Tp, is_reference<_Tp>::value>::type
        {};
        template <class _Tp, bool _IsMemberFunctionPtr>
        struct _is_function_chooser_impl :
            public false_type
        { };
        template <class _Tp>
        struct _is_function_chooser_impl<_Tp, false> :
            public _is_function_ptr_helper<_Tp*>
        { };
        template<class _Tp, bool _IsRef = true>
        struct _is_function_chooser :
            public false_type
        { };
        template <class _Tp>
        struct _is_function_chooser<_Tp, false>
        {
            static const bool value = _is_function_chooser_impl<_Tp, _is_mem_function_ptr_helper<_Tp>::value>::value;
        };
    }
    

    Um zu prüfen, ob ein Typ eine Memberfunktion einer Klasse ist, wird zunächst geprüft, ob auf den Typ nicht verwiesen wird. Dann wird ein Zeiger dieses Typs erstellt und in die Testfunktion eingesetzt. Unter Verwendung der SFINAE-Technik wählt der Compiler die für eine solche Indexierung erforderliche Überlastung der Testfunktionen aus , und basierend auf dem Ergebnis des Vergleichs mit _yes_type wird das Ergebnis generiert.

    Basierend auf einer Prüfung einer Memberfunktion einer Klasse wird eine Typenprüfung nach ihrer Zugehörigkeit zu einem Funktionstyp geschrieben. Wir prüfen, ob der Typ nicht referenziell ist. Wenn nicht, suchen wir nach einer geeigneten Spezialisierung von Musterprüfstrukturen für einen Zeiger dieses Typs, der für jeden Zeiger auf Funktionen mit bis zu 24 Parametern true_type ist.

    Und jetzt verwenden wir das Ergebnis, um is_function zu implementieren. Aus dem gleichen Grund wie im vorherigen Teil konnte ich diese Struktur nicht von integral_constant erben. Das Verhalten von integral_constant wird daher „simuliert“.

    // is_function
    template<class _Tp>
    struct is_function
    {
        static const bool value = detail::_is_function_chooser<_Tp, is_reference<_Tp>::value>::value;
        typedef const bool value_type;
        typedef integral_constant<bool, is_function::value == bool(true)> type;
        operator value_type() const
        {	// return stored value
            return (value);
        }
        value_type operator()() const
        {	// return stored value
            return (value);
        }
    };
    

    Und um is_member_function_pointer zu implementieren , ist es noch einfacher:

    // is_member_function_pointer
    template<class _Tp>
    struct is_member_function_pointer :
        public detail::_is_mem_function_ptr_helper<typename remove_cv<_Tp>::type>::type
    { };
    

    Anhand dieser Vorlagen können wir außerdem feststellen, ob ein Typ im Prinzip ein Member einer Klasse ist:

    namespace detail
    {
        template<class _Tp>
        struct _is_member_object_pointer_impl1 :
            public _not_< _or_<_is_function_ptr_helper<_Tp>, _is_mem_function_ptr_helper<_Tp> > >::type
        { };
        template<class _Tp>
        struct _is_member_object_pointer_impl2 :
            public false_type { };
        template<class _Tp, class _Cp>
        struct _is_member_object_pointer_impl2<_Tp _Cp::*> :
            public true_type { };
        template<class _Tp>
        struct _is_member_object_pointer_helper:
            public _and_<_is_member_object_pointer_impl1<_Tp>, _is_member_object_pointer_impl2<_Tp> >::type
        {};
    }
    // is_member_object_pointer
    template<class _Tp>
    struct is_member_object_pointer :
        public detail::_is_member_object_pointer_helper<typename remove_cv<_Tp>::type>::type
    { };
    

    Verwendete 'und', 'oder', 'nicht' logische Operationen für Typen aus dem ersten Teil
    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);
            }
        };
    }
    


    Hier werden logische Operationen für Typen verwendet, die letztendlich mithilfe der bedingten Vorlage den entsprechenden Vorlagentyp auswählen. Template-Programmierung in ihrer ganzen Pracht, als Ergebnis haben wir bereits bei der Kompilierung die Information, ob ein Typ Mitglied einer Klasse ist. Ziemlich "wütend", aber wie effektiv und effizient!

    Ein bisschen mehr reine Template-Programmierung auf den gleichen Logikelementen und wir haben is_fundamental , is_compound usw. Zeichen (es erfreut mich und Sie?):

    // is_arithmetic
    template<class _Tp>
    struct is_arithmetic :
        public detail::_or_<is_integral<_Tp>, is_floating_point<_Tp> >::type
    { };
    // is_fundamental
    template<class _Tp>
    struct is_fundamental :
        public detail::_or_<is_arithmetic<_Tp>, is_void<_Tp>, is_null_pointer<_Tp> >::type
    {};
    // is_object
    template<class _Tp>
    struct is_object :
        public detail::_not_< detail::_or_< is_function<_Tp>, is_reference<_Tp>, is_void<_Tp> > >::type
    {};
    // is_scalar
    template<class _Tp>
    struct is_scalar :
        public detail::_or_<is_arithmetic<_Tp>, is_pointer<_Tp>, is_member_pointer<_Tp>, is_null_pointer<_Tp>/*, is_enum<_Tp>*/ >::type
    {};
    // is_compound
    template<class _Tp>
    struct is_compound:
        public detail::_not_<is_fundamental<_Tp> >::type
    { };
    
    Ein aufmerksamer Leser wird feststellen, dass die Definition von is_enum auskommentiert ist . Tatsache ist, dass ich keine Möglichkeiten gefunden habe, Enum von anderen Typen zu unterscheiden , aber ich denke, dass dies ohne Compiler-abhängige Makros realisierbar ist. Vielleicht wird ein aufmerksamer und sachkundiger Leser seinen Weg oder Gedankengang in dieser Angelegenheit erklären.
    Um festzustellen, dass ein Typ eine Klasse ist, ist jetzt nichts erforderlich:

    namespace detail
    {
        template <class _Tp, bool _IsReference>
        struct _is_class_helper
        {
            typedef integral_constant<bool, false> type;
        };
        template <class _Tp>
        struct _is_class_helper<_Tp, false>
        {
            typedef integral_constant<bool,
                (is_scalar<_Tp>::value == bool(false))
                //&& !is_union<_Tp>::value >::value
                && (is_array<_Tp>::value == bool(false))
                && (is_void<_Tp>::value == bool(false))
                && (is_function<_Tp>::value == bool(false))> type;
        };
    }
    // is_class
    template<class _Tp>
    struct is_class :
        public detail::_is_class_helper<typename remove_cv<_Tp>::type, is_reference<_Tp>::value>::type
    { };
    

    Und alles wäre gut, aber es ist nicht möglich, eine Union in C ++ von einer Klasse im Allgemeinen zu unterscheiden. Da sie sich in ihren "äußeren Manifestationen" sehr ähnlich sind, konnten die Unterschiede (z. B. die Unmöglichkeit der Vererbung von der Union ) nicht ohne Kompilierungsfehler überprüft werden. Vielleicht sagt jemand ein kniffliges Manöver, um die Vereinigung während des Kompilierens zu bestimmen , dann wird is_class genau dem Standard entsprechen.

    Im letzten Teil dieses Kapitels werde ich besprechen, wie std :: decay und std :: common_type implementiert wurden und was noch zu type_traits hinzuzufügen ist .

    Danke für die Aufmerksamkeit.

    Jetzt auch beliebt: