¿Cómo escribí la biblioteca estándar de C ++ 11 o por qué boost es tan aterrador? Capítulo 4.1

Seguimos la aventura.

Resumen de partes anteriores


Debido a las restricciones en la capacidad de usar compiladores de C ++ 11, y por la falta de alternativa, boost quería escribir su propia implementación de la biblioteca estándar de C ++ 11 sobre la biblioteca de C ++ 98 / C ++ 03 suministrada con el compilador.

Se implementaron Static_assert , noexcept , countof , y también, después de considerar todas las características de compilador y definiciones no estándar, apareció información sobre la funcionalidad que es compatible con el compilador actual. Se incluye su propia implementación de nullptr , que se selecciona en la etapa de compilación.

Ha llegado el momento de type_traits y toda esta "plantilla mágica especial".

Enlace a GitHub con el resultado de hoy para impacientes y no lectores:

Los compromisos y las críticas constructivas son bienvenidos

Sumérgete en el mundo de la "plantilla mágica" C ++.

Tabla de contenidos


Introduccion
Capítulo 1. Viam supervadet vadens
Capítulo 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
Capítulo 3. Encontrar la implementación nullptr perfecta
Capítulo 4. Magia de plantilla de C ++
.... 4.1 Comenzamos pequeño
.... 4.2 Acerca de cuántos errores milagrosos compila el registro para nosotros
.... 4.3 Punteros y todo-todo-todo
.... 4.4 ¿Qué más se necesita para la biblioteca de plantillas?
Capitulo 5
...

Capítulo 4. Magia de plantilla de C ++


Después de terminar con las palabras clave de C ++ 11 y todos los "interruptores" dependientes de la definición entre sus implementaciones, comencé a llenar type_traits . En verdad, ya tenía bastantes clases de plantillas, similares a las estándar, que ya habían trabajado en proyectos durante mucho tiempo y, por lo tanto, me quedaba por poner todo esto en la misma forma, así como agregar la funcionalidad que faltaba.

imagen Honestamente, me inspira la programación de plantillas. Especialmente la comprensión de que todo esto es una variedad de opciones: cálculos, ramificación de código, condiciones, verificación de errores se realiza durante el proceso de compilación y nada cuesta el programa final en tiempo de ejecución. Y dado que las plantillas en C ++ son esencialmente un lenguaje de programación completo de Turing , anticipé cuán elegante y relativamente fácil sería posible implementar la parte del estándar relacionada con la programación en plantillas. Pero, para destruir inmediatamente todas las ilusiones, diré que toda la teoría de la integridad de Turing se divide en implementaciones concretas de plantillas en compiladores. Y esta parte de escribir la biblioteca, en lugar de soluciones elegantes y "trucos" de programación de plantillas, se convirtió en una lucha feroz con los compiladores, mientras que cada uno "colapsó" a su manera, y es bueno si entró en un error interno del compilador, o incluso se estrelló fuertemente con excepciones no manejadas. Lo mejor de todo se mostraron GCC (g ++), que estoicamente "masticación" todos los diseños de la plantilla y sólo se juró (en el caso) en áreas donde la necesidad de nombre de tipo explícito.

4.1 Comenzando pequeño


Comencé con plantillas simples para std :: integral_constant , std :: bool_constant y pequeñas plantillas similares.

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

Basado en condicional, puede ingresar plantillas convenientes para operaciones lógicas {"y", "o", "no"} sobre tipos (¡y todas estas operaciones se consideran correctas en la etapa de compilación! Es genial, ¿no?):

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

Tres puntos merecen atención aquí:

1) Es importante poner siempre un espacio entre los corchetes angulares ('<' y '>') de las plantillas, ya que antes de C ++ 11 no había ninguna aclaración en el estándar sobre cómo interpretar '>>' y '<<' en código como _o _ <_ B2, _o _ <_ B3, _B4 >> , y por lo tanto, casi todos los compiladores trataron esto como un operador de cambio de bit, lo que conduce a un error de compilación.

2) En algunos compiladores (Visual Studio 6.0, por ejemplo) hubo un error que consistió en el hecho de que era imposible usar el tipo vacío como parámetro de plantilla. Para estos fines, se introduce un tipo void_type separado en el pasaje anterior para reemplazar el tipo void donde se requiere el valor del parámetro de plantilla predeterminado.

3) Los compiladores muy antiguos (Borland C ++ Builder, por ejemplo) tenían un tipo bool implementado de forma torcida, que en algunas situaciones "repentinamente" se convirtió en int ( verdadero -> 1, falso -> 0), así como tipos de variables estáticas constantes del tipo bool (y no solo ellos), si estuvieran contenidos en clases de plantilla. Debido a todo este lío, como resultado, para una comparación completamente inofensiva en el estilo de my_template_type :: static_bool_value == false, el compilador podría emitir fácilmente un encantamiento que no puede lanzar 'tipo indefinido' a int (0) o algo así. Por lo tanto, es necesario tratar siempre de indicar explícitamente el tipo de valores para la comparación, ayudando así al compilador a determinar con qué tipos se trata.


Agregue más trabajo con valores constantes y volátiles . Primero, el remove_ ... trivialmente implementado donde simplemente especializamos la plantilla para ciertos modificadores de tipo: si el tipo con el modificador entra en la plantilla, el compilador debe, después de mirar todas las especializaciones (recuerde el principio SFINAE del capítulo anterior ) de la plantilla, seleccionar la más adecuada (con indicación explícita del modificador deseado) :

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

Y luego implementamos plantillas add_ ... donde todo ya es un poco más complicado:

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

Aquí procesamos cuidadosamente los tipos de referencia por separado para no perder el enlace. Además, no nos olvidaremos de los tipos de funciones que es imposible hacer volátiles o constantes en principio, por lo tanto, los dejaremos "tal cual". Puedo decir que todo esto parece muy simple, pero este es exactamente el caso cuando "el demonio está en los detalles", o mejor dicho, "los errores están en los detalles de la implementación".

El final de la primera parte del cuarto capítulo. En la segunda parte , hablaré sobre cuán difícil se da la programación de plantillas al compilador, y también habrá una magia de plantillas más genial. Ah, y sin embargo, por qué es largo, no es una constante integral, según algunos compiladores hasta el día de hoy.

Gracias por su atencion

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


All Articles