📜 ⬆️ ⬇️

As 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 our own implementation of the C ++ 11 standard library on top of the C ++ 98 / C ++ 03 library supplied with the compiler.

Were implemented static_assert , noexcept , countof , as well as, after considering all the non-standard defines and features of the compilers, there was information about the functionality that is supported by the current compiler. Included is its own implementation of nullptr , which is selected at compile time.

The time has come for type_traits and all this “special patterned magic”.
')
Link to GitHub with the result today for the impatient and non-readers:

Commits and constructive criticism are welcome

Immerse yourself 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 implementation of nullptr
Chapter 4. C ++ Template "Magic"
.... 4.1 Starting small
.... 4.2 How many we get wonderful errors are compiled by the log
.... 4.3 Pointers and all-all-all
.... 4.4 What else is needed for the template 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.

image Honestly tell you that I am inspired by template programming. Especially the realization that all this variety of options: calculations, code branching, conditions, checking for errors is performed during the compilation process and does not cost anything to the final program at the execution stage. And since templates in C ++ are in essence a 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 programming on templates. 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 template programming turned into a fierce struggle with compilers, despite the fact that everyone "collapsed" in its own way, and it’s good if you’ve lost your own internal compiler error, or even crashes 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 explicit typename .

4.1 Starting small


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

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

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! Great, isn't it?):

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

Three points deserve attention here:

1) It is important to put a space between the angle brackets ('<' and '>') of templates everywhere, since before C ++ 11 the standard didn’t specify how to interpret '>>' and '<<' in _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 like that. Therefore, you should always try to explicitly specify the type of values ​​for comparison, thereby helping the compiler to decide which types it has.


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 - if the compiler is required to type into a template with a 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> 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; }; 

And then we implement add_ templates ... where everything is already a bit 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 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; }; 

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

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". Oh, and also - why long long is not an integral constant in the opinion of some compilers to this day.

Thank you for attention.

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


All Articles