How I wrote the standard C ++ 11 library or why the boost is so scary. Chapter 4.3

    We continue the adventure.

    Summary of the previous parts


    Due to limitations on the ability to use C ++ 11 compilers and from the lack of alternatives to boost, there was a desire to write your own implementation of the standard C ++ 11 library on top of the C ++ 98 / C ++ 03 library supplied with the compiler. The static_assert , noexcept , countof , and also after consideration

    were implemented of all non-standard defines and features of compilers, there is information about the functionality supported by the current compiler. Included is its own implementation of nullptr , which is selected at the compilation stage. It's time to type_traits

    and all this "special patterned magic." In the previous parts of this chapter, we looked at my implementation of the basic templates of the standard library, and in this part we will discuss the combination of the SFINAE technique with templates and a little about code generation.

    Link to GitHub with the result for today for impatient and non-readers:
    Commits and constructive criticism are welcome
    More C ++ templates under the cat.

    Table of contents


    Introduction
    Chapter 1. Viam supervadet vadens
    Chapter 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
    Chapter 3. Finding the perfect realization nullptr
    Chapter 4. Patterned "magic» the C ++
    .... 4.1 start small
    .... 4.2 About how many mistakes we Wonderful compilations are prepared by the log
    .... 4.3 Pointers and all-all-all
    .... 4.4 What else is needed for the sample library
    Chapter 5.
    ...

    Chapter 4. C ++ Template "Magic". Continuation


    4.3 Pointers and all-all


    At this stage, I just had to get information about whether the type is an array for std :: is_array and you could proceed to the templates for pointers. The implementation was also trivial, but not without assumptions.

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

    A simple template specialization for arrays of a given length “catches” all types of arrays, but the problem occurs with the incomplete type T [] (an array without specifying a length). The fact is that this type is not defined by some compilers (C ++ Builder) when specializing a template, and I haven’t yet found a universal solution here.

    After the library was “taught” to define built-in types, alignment in type memory, working with type modifiers and other basic things through templates at compile time, it is time for pointers and references.

    imageIn C ++, you can select two groups of pointers - pointers to class members and pointers to other objects. Why is this separation important for the further implementation of the standard library? The fact is that pointers to class members have a significant difference from other pointers by the presence of this , i.e. pointer to an object of this class. And according to the standard, pointers to a member of a class have a separate syntax for definition, are a separate type and cannot be represented through a regular pointer. In practice, this is expressed in that the size of a pointer to a member of a class is usually larger than the size of a regular pointer (which is == sizeof (void *) ), since for implementing virtual member functions of the class, as well as storing the this pointercompilers usually implement pointers to a class member as a structure (read about virtual functions and structure ). How to submit pointers to class members is left, according to the standard, at the discretion of the compiler, but we will remember this difference in size and representation when considering further code.

    To define a regular pointer to an object, we will write a simple is_pointer template , as well as the is_lvalue_reference template for references to the object ( we will postpone the is_rvalue_reference, because up to the 11th standard of both the && operator and the whole move-semantics did not exist):

    namespace detail
    {
        template<class>
        struct _is_pointer_helper :public false_type { };
        template<class _Tp>
        struct _is_pointer_helper<_Tp*> :public true_type { };
    }
    // is_pointertemplate<class _Tp>
    structis_pointer :public detail::_is_pointer_helper<typename remove_cv<_Tp>::type>::type
    { };
    // is_lvalue_referencetemplate<class>
    structis_lvalue_reference :public false_type { };
    template<class _Tp>
    structis_lvalue_reference<_Tp&> :public true_type { };
    

    There is already something fundamentally new here, all the same has been done in the previous parts of this chapter. We continue the definitions of pointers to objects - now consider the pointers to functions.
    It is important to understand that a function and a member function of a class are completely different entities according to the standard:

    • The first pointer will be normal (a pointer to an object), the second will be a pointer to a member of the class.

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

    • You can create a link to the first one (object link), and you cannot create a second link.

    void (&func_ref)(int); // ссылка 'func_ref' на функцию вида 'void func(int){}'//-------------------- // ссылка на функцию-член класса не определена стандартом
    Here I will just mention a little about code generation. Since before C ++ 11 there were no templates with a variable number of parameters, all templates where there could be a different number of parameters were determined through the specialization of the main template with whatever large number of parameterson input and their initialization by default dummy parameters. The same applied to function overloads, since There were no macros with a variable number of parameters either. Since writing hands on 60-70 lines of the same type template specializations, overloading functions is a rather dull and useless exercise, and also fraught with the possibility that I can make a mistake, I wrote a simple generator of code templates and overload functions for these purposes. I decided to limit the definition of functions to 24 parameters and it looks in the code rather cumbersome, but simple and clear:

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

    ...
    template <classR, classT0, classT1, classT2, classT3, classT4, classT5, classT6, classT7, classT8, classT9, classT10, classT11, classT12, classT13, classT14, classT15, classT16, classT17, classT18, classT19, classT20, classT21, classT22, classT23, classT24>
        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 <classR, classT0, classT1, classT2, classT3, classT4, classT5, classT6, classT7, classT8, classT9, classT10, classT11, classT12, classT13, classT14, classT15, classT16, classT17, classT18, classT19, classT20, classT21, classT22, classT23, classT24>
        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 {};
    }
    

    We define the types familiar to the previous chapter for the SFINAE technique:

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

    Some more macros for convenience.
    namespace detail
    {
        #define _IS_MEM_FUN_PTR_CLR \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*constvolatile*)(ARGS)); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*constvolatile*)(ARGS...)); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*constvolatile*)(ARGS) const); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*constvolatile*)(ARGS) volatile); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*constvolatile*)(ARGS) constvolatile); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*constvolatile*)(ARGS...) const); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*constvolatile*)(ARGS...) volatile); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*constvolatile*)(ARGS...) constvolatile);
    #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::*constvolatile*)(ARGS)); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(__cdeclT::*constvolatile*)(ARGS) const); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(__cdeclT::*constvolatile*)(ARGS) volatile); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(__cdeclT::*constvolatile*)(ARGS) constvolatile);
    #define _IS_MEM_FUN_STDCALL_PTR \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcall T::*constvolatile*)(ARGS)); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcallT::*constvolatile*)(ARGS) const); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcallT::*constvolatile*)(ARGS) volatile); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcallT::*constvolatile*)(ARGS) constvolatile);
    #define _IS_MEM_FUN_FASTCALL_PTR \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcall T::*constvolatile*)(ARGS)); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcallT::*constvolatile*)(ARGS) const); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcallT::*constvolatile*)(ARGS) volatile); \
    		template <classR, classTTYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcallT::*constvolatile*)(ARGS) constvolatile);
    #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
    }
    


    Macros are defined so that it is relatively convenient to override TYPES and ARGS defines as a list of types and parameters, then substituting the _IS_MEM_FUN_PTR macro to generate definitions for all possible types of functions by the preprocessor. It is also worth paying attention to the fact that for the compilers of Microsoft, another calling convention is important ( __ fastcall , __stdcall and __ cdecl ), since with different conventions, the functions will be different, although the set of arguments and the return value are the same. As a result, the whole grandiose construction of macros is used rather compactly:

    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
    }
    

    And now for the sake of what all this was written:

    namespace detail
    {
        template <class _Tp, bool _IsRef>
        struct _is_mem_function_ptr_impl
        {static _Tp *p;
            staticconstbool value = (sizeof(_is_mem_function_ptr(_is_mem_function_ptr_impl::p)) == sizeof(_yes_type));
            typedeftypename 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>
        {staticconstbool value = _is_function_chooser_impl<_Tp, _is_mem_function_ptr_helper<_Tp>::value>::value;
        };
    }
    

    To check whether a type is a member function of a class, it is first checked that the type is not referenced. Then a pointer of this type is created and substituted into the probe function. Using the SFINAE technique, the compiler selects the necessary of the overloads of the probe functions for such an index and, based on the result of the comparison with _yes_type , the result is generated.

    Based on a check on a member function of a class, a type check is written on its belonging to a function type. We check if the type is not referential, if not, then we are looking for a suitable specialization of template probing structures for a pointer of this type, which will be true_type for any pointers to functions with up to 24 parameters.

    And now we use the result to implement is_function. Here, for the same reason as in the previous part , I could not inherit this structure from integral_constant , so the behavior of integral_constant is “simulated”.

    // is_functiontemplate<class _Tp>
    structis_function
    {staticconstbool value = detail::_is_function_chooser<_Tp, is_reference<_Tp>::value>::value;
        typedefconstbool value_type;
        typedef integral_constant<bool, is_function::value == bool(true)> type;
        operatorvalue_type()const{	// return stored valuereturn (value);
        }
        value_type operator()()const{	// return stored valuereturn (value);
        }
    };
    

    And to implement is_member_function_pointer, it's still easier:

    // is_member_function_pointertemplate<class _Tp>
    structis_member_function_pointer :public detail::_is_mem_function_ptr_helper<typename remove_cv<_Tp>::type>::type
    { };
    

    Further, based on these templates, we can determine whether a type is in principle a member of a class:

    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_pointertemplate<class _Tp>
    structis_member_object_pointer :public detail::_is_member_object_pointer_helper<typename remove_cv<_Tp>::type>::type
    { };
    

    Used 'and', 'or', 'not' logical operations on types from the first part
    namespace detail
    {
        structvoid_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_
        {staticconstbool value = !bool(_Pp::value);
            typedefconstbool value_type;
            typedef integral_constant<bool, _not_::value == bool(true)> type;
            operatorvalue_type()const{	// return stored valuereturn (value);
            }
            value_type operator()()const{	// return stored valuereturn (value);
            }
        };
    }
    


    Here, logical operations on types are used, which, using the conditional template , ultimately select the appropriate template type. Template programming in all its glory, as a result, at the compilation stage, we already have information on whether a type is a member of a class. Quite “furious”, but how effective and efficient!

    A bit more pure template programming on the same logic elements and we have is_fundamental , is_compound , etc. signs (it delights me, and you?):

    // is_arithmetictemplate<class _Tp>
    structis_arithmetic :public detail::_or_<is_integral<_Tp>, is_floating_point<_Tp> >::type
    { };
    // is_fundamentaltemplate<class _Tp>
    structis_fundamental :public detail::_or_<is_arithmetic<_Tp>, is_void<_Tp>, is_null_pointer<_Tp> >::type
    {};
    // is_objecttemplate<class _Tp>
    structis_object :public detail::_not_< detail::_or_< is_function<_Tp>, is_reference<_Tp>, is_void<_Tp> > >::type
    {};
    // is_scalartemplate<class _Tp>
    structis_scalar :public detail::_or_<is_arithmetic<_Tp>, is_pointer<_Tp>, is_member_pointer<_Tp>, is_null_pointer<_Tp>/*, is_enum<_Tp>*/ >::type
    {};
    // is_compoundtemplate<class _Tp>
    structis_compound:public detail::_not_<is_fundamental<_Tp> >::type
    { };
    
    An attentive reader will notice that the definition of is_enum is commented out. The fact is that I haven’t found any ways to distinguish enum from other types, but I think that it is realizable without using compiler-dependent macros. Perhaps attentive and knowledgeable reader will tell his way or course of thoughts on this matter.
    To determine the fact that a type is a class, now nothing is needed:

    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_classtemplate<class _Tp>
    structis_class :public detail::_is_class_helper<typename remove_cv<_Tp>::type, is_reference<_Tp>::value>::type
    { };
    

    And everything would be fine, but it is not possible to distinguish a union in C ++ from a class in general. Because they are very similar in their "external manifestations", and the differences (for example, the impossibility of inheritance from union ) could not be verified without compilation errors. Perhaps someone will tell a tricky maneuver to determine the union during compilation, then is_class will exactly match the standard.

    In the final part of this chapter, I will discuss how std :: decay and std :: common_type were implemented , as well as what else to add to type_traits .

    Thank you for attention.

    Also popular now: