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

    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. The time has come for type_traits and all this “special patterned magic”. Link to GitHub with the result for today for impatient and non-readers:





    Commits and constructive criticism are welcome

    Let us dive into the world of "template magic" C ++.

    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"


    Having finished with the C ++ 11 keywords and all define-dependent “switches” between their implementations, I began to fill in type_traits . To tell the truth, I already had quite a few template classes, similar to the standard ones, which had already been working in projects for quite a long time and therefore it was necessary to bring all this into the same form, as well as to add the missing functionality.

    imageHonestly tell you that I am inspired by template programming. Especially the realization that all this variety of options: calculations, code branching, conditions, error checking is performed during the compilation process and does not cost anything to the final program at the execution stage. And since patterns in C ++ are essentially Turing-complete programming language, I was in anticipation of how gracefully and relatively easy it would be to implement the part of the standard associated with template programming. But, in order to immediately destroy all illusions, I will say that the whole theory about Turing completeness is broken about the concrete implementation of patterns in compilers. And this part of writing the library instead of elegant solutions and "tricks" of patterned programming turned into a fierce struggle with compilers, despite the fact that each "collapsed" in its own way, and it’s good if you’ve lost your own internal compiler error or even crashed with raw exceptions. GCC (g ++) showed itself best of all; it stoically “chewed on” all patterned constructions and only cursed (in the case) in places where there was not enough obvious typename .

    4.1 Starting small


    I started with simple templates for std :: integral_constant , std :: bool_constant and similar small templates.

    template<class _Tp, _TpVal>
    structintegral_constant
    {// convenient template for integral constant typesstaticconst _Tp value = Val;
        typedefconst _Tp value_type;
        typedef integral_constant<_Tp, Val> type;
        operatorvalue_type()const{	// return stored valuereturn (value);
        }
        value_type operator()()const{	// return stored valuereturn (value);
        }
    };
    typedef integral_constant<bool, true> true_type;
    typedef integral_constant<bool, false> false_type;
    template<bool Val>
    structbool_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>
    structconditional
    {typedef _Iftrue type;
    };
    // Partial specialization for false.template<class _Iftrue, class _Iffalse>
    structconditional<false, _Iftrue, _Iffalse>
    {typedef _Iffalse type;
    };
    

    On the basis of conditional, you can enter convenient templates for logical operations {"and", "or", "not"} over types (And all these operations are considered right at the compilation stage! Nice, isn't it?):

    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);
            }
        };
    }
    

    Three points deserve attention here:

    1) It is important to put a space between angle brackets ('<' and '>') for templates everywhere, because before C ++ 11 the standard didn’t specify how to interpret '>>' and '<<' in code like _or _ <_ B2, _or _ <_ B3, _B4 >> , and therefore almost all compilers interpreted this as a bit shift operator, which leads to a compilation error.

    2) In some compilers (Visual Studio 6.0 for example) there was a bug which consisted in the fact that it was impossible to use the void type as a template parameter. For these purposes, a separate void_type type is introduced in the excerpt above to replace the void type where a default parameter value is required.

    3) Very old compilers (Borland C ++ Builder for example) had a crookedly implemented bool type , which in some situations “suddenly” turned into int ( true -> 1, false -> 0), and also did not display types of constant static variables bool (and not only them) if they were contained in the template classes. Because of all this mess in the end for a completely innocuous comparison in the style of my_template_type :: static_bool_value == false, the compiler could easily produce an enchanting cannon cast 'undefined type' to int (0)or something similar. Therefore, it is necessary to always try to explicitly specify the type of values ​​for comparison, thereby helping the compiler to determine which types it is dealing with.


    Let's add more work with const and volatile values. First, a trivially implemented remove_ ... where we simply specialize the template for certain type modifiers - in case the compiler is obliged to type into the template with the modifier, having reviewed all the specializations (recall the SFINAE principle from the previous chapter ) of the template, choose the most suitable (with the explicit indication of the desired modifier) :

    template<class _Tp>
    structis_function;template<class _Tp>
    structremove_const
    {// remove top level const qualifiertypedef _Tp type;
    };
    template<class _Tp>
    structremove_const<const _Tp>
    {// remove top level const qualifiertypedef _Tp type;
    };
    template<class _Tp>
    structremove_const<const volatile _Tp>
    {// remove top level const qualifiertypedefvolatile _Tp type;
    };
    // remove_volatiletemplate<class _Tp>
    structremove_volatile
    {// remove top level volatile qualifiertypedef _Tp type;
    };
    template<class _Tp>
    structremove_volatile<volatile _Tp>
    {// remove top level volatile qualifiertypedef _Tp type;
    };
    // remove_cvtemplate<class _Tp>
    structremove_cv
    {// remove top level const and volatile qualifierstypedeftypename remove_const<typename remove_volatile<_Tp>::type>::type
            type;
    };
    

    And then we implement add_ templates ... where everything is already a little more complicated:

    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 constvolatile type;
        };
        template<class _Tp>
        struct _add_cv_helper<_Tp, true>
        {typedef _Tp type;
        };
    }
    // add_consttemplate<class _Tp>
    structadd_const:public detail::_add_const_helper<_Tp, is_function<_Tp>::value>
    {
    };
    template<class _Tp>
    structadd_const<_Tp&>
    {typedef _Tp &     type;
    };
    // add_volatiletemplate<class _Tp>
    structadd_volatile :public detail::_add_volatile_helper<_Tp, is_function<_Tp>::value>
    {
    };
    template<class _Tp>
    structadd_volatile<_Tp&>
    {typedef _Tp &     type;
    };
    // add_cvtemplate<class _Tp>
    structadd_cv :public detail::_add_cv_helper<_Tp, is_function<_Tp>::value>
    {
    };
    template<class _Tp>
    structadd_cv<_Tp&>
    {typedef _Tp &     type;
    };
    

    Here we will carefully handle reference types separately in order not to lose the link. Also, let's not forget about the types of functions that cannot be made volatile or const in principle, therefore we will leave them “as is”. I can say that all this looks very simple, but this is exactly the case when the “devil is in the details,” or rather, “the bugs are in the implementation details.”

    The end of the first part of the fourth chapter. In the second part I will talk about how hard the template programming is given to the compiler, and there will also be more cool template "magic". Ah, and also - why long long is not an integral constant in the opinion of some compilers to this day.

    Thank you for attention.

    Also popular now: