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

    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 first part, we looked at my implementation of the simplest templates of the standard library, but now we will climb "deeper" into the templates.

    Link to GitHub with the result for today for impatient and non-readers:

    Commits and constructive criticism are welcome

    Continued immersion in 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". Continuation


    4.2 How many marvelous errors to us are prepared by compilations of the log


    In the first part of this chapter, the basic type_traits templates were introduced , but a few more were missing for the complete set.

    For example, the is_integral and is_floating_point templates , which are actually very trivial defined, were simply necessary - through the template specialization for each built-in type. The question here only arose with "big" types of long long . The fact is that this type as built-in appears in the C ++ standard only from version 11. And it would be logical to assume that it all comes down to checking the version of the C ++ standard (which is definitely difficult to determine ), but it was not there.

    imageBecause since 1999 there is a standard for the C language C99, in which the types long long int and unsigned long long int were already present (since 1999!), And since the C ++ language sought to maintain backward compatibility with pure C, many compilers (which were usually mixed C / C ++) just added it as a fundamental type even before the release of even the standard C ++ 03. That is, it turned out that the built-in type is in fact (from C), but it is not described in the C ++ standard and should not be there. And this brings a little more confusion to the implementation of the standard library. But let's look at the code:

    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> 
    structis_floating_point :public detail::_is_floating_point<typename remove_cv<_Tp>::type> 
    { };
    

    With the code above, everything is clear - we specialize the template into the necessary types with a floating point, and, after “cleaning” from type modifiers, we say “yes” or “no” to the type transferred to us. Next in line are the integer types:

    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_MAXtemplate<> struct _is_integral_impl<unsigned long long int> :public true_type {};
        #endiftemplate<> 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_MAXtemplate<> struct _is_integral_impl<long long int> :public true_type {};
        #endiftemplate <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>
    structis_integral :public detail::_is_integral<typename remove_cv<_Tp>::type>
    { };
    

    Here it is necessary to stop and think a little. For "old" integer types like int , bool , etc. we do the same specialization as is_floating_point . For the “new” types of long long int and its unsigned counterpart, we define overloads only when there is a define LLONG_MAX , which was defined in C ++ 11 (as the first C ++ standard that is compatible with C99), and should be defined in the climits header file as maximum a large number that fits in a long long int object . In climitsthere are also several macro definitions (for the smallest possible number and unsigned equivalents), but I decided to use this macro, which is not essential. It is important that, unlike boost, in this implementation the “big” types from C will not be defined as integer constants, although they are (possibly) present in the compiler. What else is important is the types char16_t and char32_t , which were also introduced in C ++ 11, but they were not delivered in C99 (they appeared simultaneously with C ++ in C standard C11), and therefore in old standards their definition to be only through a type alias (for example typedef short char16_t, but more on that later). If so, then in order for the template specialization to handle situations correctly and when these types are separate (built-in) and when they are defined via typedef , another layer of the detail specialization of the detail template :: _ is_integral is needed .

    The interesting fact is that in some older compilers, these C-shnye "big" types are not an integral constant . What can be understood and even forgive, since these types are non-standard for C ++ up to 11 standards, and generally they should not be there. But what is difficult to understand is that these types in the newest C ++ creative compiler Embarcadero (Embarcadero C ++ Builder), which C ++ 11 supposedly supports, are still not an integral constant in their 32-bit builds (like 20 years ago , then it was Borland still true). Probably because of this, including a large part of the standard C ++ library 11 is absent in most of these 32-bit builds (#include ratio? Chrono? Will manage). Embarcadero seems to have decided to force the onset of the 64-bit era with the motto: “Do you want a C ++ 11 or newer standard? Build a 64-bit program (and only clang, our compiler cannot)! ”.

    Having finished the proceedings with the fundamental types of language, we introduce some more simple patterns:

    Simple templates
    template <bool, class _Tp = detail::void_type>
    structenable_if
    { };
    template <class _Tp>
    structenable_if<true, _Tp>
    {typedef _Tp type;
    };
    template<class, class>
    structis_same :public false_type
    { };
    template<class _Tp>
    structis_same<_Tp, _Tp> :public true_type//specialization
    { };
    template <class _Tp>
    structis_const :public false_type
    { };
    template <class _Tp>
    structis_const<const _Tp> :public true_type
    { };
    template <class _Tp>
    structis_const<const volatile _Tp> :public true_type
    { };
    /// is_volatiletemplate<class>
    structis_volatile
        :public false_type
    { };
    template<class _Tp>
    structis_volatile<volatile _Tp>
        :public true_type
    { };
    template<class _Tp>
    structis_volatile<const volatile _Tp>
        :public true_type
    { };
    


    It deserves attention unless the fact that templates specialize for all type modifiers ( volatile and const volatile for example), because some compilers tend to “lose” one of the modifiers when a template is opened.

    Separately, I highlight the implementation of is_signed and is_unsigned :

    namespace detail
    {
        template<bool>
        struct _sign_unsign_chooser;template<class _Tp>
        struct _signed_comparer
        {staticconstbool value = _Tp(-1) < _Tp(0);
        };
        template<class _Tp>
        struct _unsigned_comparer
        {staticconstbool value = _Tp(0) < _Tp(-1);
        };
        template<bool Val>
        struct _cat_base :
            integral_constant<bool, Val>
        {	// base class for type predicates
        };
        template<>
        struct _sign_unsign_chooser<true>//integral
        {template<class _Tp>
            struct _signed :public _cat_base<_signed_comparer<typename remove_cv<_Tp>::type>::value>
            {
            };
            template<class _Tp>
            struct _unsigned :public _cat_base<_unsigned_comparer<typename remove_cv<_Tp>::type>::value>
            {
            };
        };
        template<>
        struct _sign_unsign_chooser<false>//floatingpoint
        {template<class _Tp>
            struct _signed :public is_floating_point<_Tp>
            {
            };
            template<class _Tp>
            struct _unsigned :public false_type
            {
            };
        };
    }
    template<classT>
    structis_signed
    {// determine whether T is a signed typestaticconstbool value = detail::_sign_unsign_chooser<is_integral<T>::value>::template _signed<T>::value;
        typedefconstbool value_type;
        typedef integral_constant<bool, is_signed::value == bool(true)> type;
        operatorvalue_type()const{	// return stored valuereturn (value);
        }
        value_type operator()()const{	// return stored valuereturn (value);
        }
    };
    template<classT>
    structis_unsigned
    {// determine whether T is an unsigned typestaticconstbool value = detail::_sign_unsign_chooser<is_integral<T>::value>::template _unsigned<T>::value;
        typedefconstbool value_type;
        typedef integral_constant<bool, is_unsigned::value == bool(true)> type;
        operatorvalue_type()const{	// return stored valuereturn (value);
        }
        value_type operator()()const{	// return stored valuereturn (value);
        }
    };
    

    When implementing this part, I entered into an unequal fight with Borland C ++ Builder 6.0, which did not want to make these two templates the successors of integral_constant , which in the end, after dozens of internal compiler error, resulted in an “imitation” of the integral_constant behavior for these templates. Here, it may be worthwhile to still struggle and come up with some kind of clever deduction of the type is_ * un * signed: integral_constant through templates, but for the time being I have postponed this task as not a priority. Interestingly, in the code section above, it is determined at compile time that the type is unsigned / signed. To begin with, all non-integer types are swept aside and for them the template goes into a separate specialized _sign_unsign_chooser branch with a template argumentfalse , which in turn always returns value == false for any types other than standard floating point types (they are always signed for obvious reasons, so _signed :: value is true ). For integer types, simple, but from that quite interesting checks are performed. Here we use the fact that for unsigned integer types, with decreasing and subsequent “passing” through a minimum (0 obviously) an overflow occurs and the number acquires its maximum possible value.

    This fact is well-known, as well as the fact that for iconic types overflow is undefined behavior and this should be monitored (according to the standard, you cannot reduce the intvariable is less than INT_MIN and hope that as a result of the overflow, get INT_MAX (not 42 or formatted hard disk).

    We write _Tp (-1) <_Tp (0) to check for “signedness” of the type using this fact, then for unsigned types, -1 is “transformed” through overflow to the maximum number of this type, whereas for sign-based ones, the comparison will be done without overflowing, and will be compared with -1 from 0.

    And the last for today, but not the last "trick" in my library is the implementation of the alignment_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
        {staticconststd::size_t value = A < S ? A : S;
        };
        template <unsigned A>
        struct _alignment_logic_helper<A, 0>
        {staticconststd::size_t value = A;
        };
        template <unsigned S>
        struct _alignment_logic_helper<0, S>
        {staticconststd::size_t value = S;
        };
        template< class _Tp >
        struct _alignment_of_impl
        {#if _MSC_VER > 1400//// With MSVC both the build in __alignof operator// and following logic gets things wrong from time to time// Using a combination of the two seems to make the most of a bad job://staticconststd::size_t value =
                (_alignment_logic_helper<
                    sizeof(_alignment_of_trick<_Tp>) - sizeof(_Tp),
                    __alignof(_Tp)
                >::value);
        #elsestaticconststd::size_t value =
                (_alignment_logic_helper<
                    sizeof(_alignment_of_trick<_Tp>) - sizeof(_Tp),
                    sizeof(_Tp)
                >::value);
        #endiftypedef integral_constant<std::size_t, std::size_t(_alignment_of_impl::value)> type;
        private:
            typedef intern::type_traits_asserts check;
            typedeftypename check::alignment_of_type_can_not_be_zero_assert< _alignment_of_impl::value != 0 >::
                alignment_of_type_can_not_be_zero_assert_failed
            check1; // if you are there means aligment of type passed can not be calculated or compiler can not handle this situation (sorry, nothing can be done there)
        };
        // borland compilers seem to be unable to handle long double correctly, so this will do the trick:struct _long_double_wrapper{longdouble value; };
    }
    template <class _Tp> 
    structalignment_of:public detail::_alignment_of_impl<_Tp>::type
    {};
    template <class _Tp> 
    structalignment_of<_Tp&>:public alignment_of<_Tp*>
    {};
    template<> 
    structalignment_of<long double>:public alignment_of<detail::_long_double_wrapper>
    {};
    

    Here, Microsoft again distinguished itself with their Visual Studio, which even with a built-in nonstandard __alignof build-in macro still produces incorrect results when using it.

    Explanation from boost
    Visual C++ users should note that MSVC has varying definitions of «alignment». For example consider the following code:

    typedeflonglongalign_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 this code, even though boost::alignment_of<align_t> reports that align_t has 8-byte alignment, the final assert will fail for a 32-bit build because a1 is not aligned on an 8 byte boundary. Note that had we used the MSVC intrinsic __alignof in place of boost::alignment_of we would still get the same result. In fact for MSVC alignment requirements (and promises) only really apply to dynamic storage, and not the stack.


    Let me remind you what the std :: alignment_of template should do - return a value that represents the requirements for placing an element of this type in memory. If we digress a little, then an element of each type has some kind of placement in memory, and if it is continuous for an array of elements, then, for example, classes may well have “holes” between class members ( sizeof class struct { char a;} is most likely not equal to 1, although there is 1 byte of everything inside, because the compiler aligns it to 1 + 3 bytes in the optimization process).

    And now let's look at the code again. Declare the structure _alignment_of_trickin which we place with the “indent” from memory into 1 byte element of the type being checked. And check the alignment simply by subtracting the size of the type being checked from the size of the resulting structure. In other words, if the compiler decides to “insert” the empty space between the element of the type being checked and the previous char , we will get the value of the type alignment in the structure.

    Also here, static assert is first encountered as a type. They are declared as:

    namespace intern
    {
        // since we have no static_assert in pre-C++11 we just compile-time assert this way:structtype_traits_asserts
        {template<bool>
            structmake_signed_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert;template<bool>
            structmake_unsigned_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert;template<bool>
            structnot_allowed_arithmetic_type_assert;template<bool>
            structalignment_of_type_can_not_be_zero_assert;
        };
        template<>
        structtype_traits_asserts::make_signed_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert<true>
        {
            typedefbool make_signed_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert_failed;
        };
        template<>
        structtype_traits_asserts::make_unsigned_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert<true>
        {
            typedefbool make_unsigned_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert_failed;
        };
        template<>
        structtype_traits_asserts::not_allowed_arithmetic_type_assert<true>
        {
            typedefbool not_allowed_arithmetic_type_assert_failed;
        };
        template<>
        structtype_traits_asserts::alignment_of_type_can_not_be_zero_assert<true>
        {
            typedefbool alignment_of_type_can_not_be_zero_assert_failed;
        };
    }
    

    In fact, these specialized templates are needed to replace static_assert from C ++ 11, which is located inside the class definition. Such assert are more lightweight and highly specialized than the general implementation of STATIC_ASSERT from Chapter 2 , and allow you not to pull the header file core.h into type_traits .

    imageMany templates? It will be even more! At this stop yet, as further will the fascinating story of combining template programming technique SFINAE, as well as about what I had to write a small code generator.

    Thank you for attention.

    Also popular now: