
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". En la primera parte, examinamos mi implementación de las plantillas más simples de la biblioteca estándar, pero ahora profundizaremos en las plantillas.
Enlace a GitHub con el resultado de hoy para impacientes y no lectores:
Los compromisos y las críticas constructivas son bienvenidos
Continuación de la inmersión en el mundo de la "plantilla mágica" C ++.
Tabla de contenidos
IntroduccionCapítulo 1. Viam supervadet vadensCapítulo 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endifCapítulo 3. Encontrar la implementación nullptr perfectaCapí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. Plantilla "mágica" C ++. Continuación
4.2 Acerca de cuántos errores milagrosos compila el registro
En la
primera parte de este capítulo, se introdujeron las plantillas básicas de
type_traits , pero faltaban algunas más para el conjunto completo.
Por ejemplo, simplemente se necesitaban las plantillas
is_integral y
is_floating_point , que en realidad se definen de manera muy trivial, a través de la especialización de plantilla para cada tipo incorporado. La pregunta aquí solo surgió con los tipos "grandes" de
largo largo . El hecho es que este tipo como incorporado aparece en el estándar del lenguaje C ++ solo a partir de la versión 11. Y sería lógico suponer que todo se reduce a verificar la versión del estándar C ++ (que de
todos modos es difícil de determinar ), pero no estaba allí.

Porque, desde 1999, existe el estándar de lenguaje C99 C en el que los tipos
long long int y
unsigned long long int ya han estado presentes (¡desde 1999!), Y dado que el lenguaje C ++ buscó mantener la compatibilidad con versiones anteriores de C puro, muchos compiladores (que generalmente se mezclaron C / C ++) simplemente lo añadieron como un tipo fundamental incluso antes de que se lanzara el estándar C ++ 03. Es decir, la situación era que el tipo incorporado es de hecho (de C), pero no se describe en el estándar C ++ y no debería estar allí. Y eso agrega un poco más de confusión a la implementación de la biblioteca estándar. Pero veamos el código:
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> struct is_floating_point : public detail::_is_floating_point<typename remove_cv<_Tp>::type> { };
Todo está claro con el código anterior: especializamos la plantilla para los tipos de punto flotante necesarios y, después de "borrar" los modificadores de tipo, decimos "sí" o "no" al tipo que se nos pasa. Los siguientes en la línea son los tipos enteros:
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_MAX template<> struct _is_integral_impl<unsigned long long int> : public true_type {}; #endif template<> 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_MAX template<> struct _is_integral_impl<long long int> : public true_type {}; #endif template <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> struct is_integral : public detail::_is_integral<typename remove_cv<_Tp>::type> { };
Aquí debes detenerte un poco y pensar. Para tipos enteros "antiguos" como
int ,
bool , etc. hacemos las mismas especializaciones que con
is_floating_point . Para los "nuevos" tipos
long long int y su contraparte sin signo, definimos sobrecargas solo si hay una
definición LLONG_MAX, que se definió en C ++ 11 (como el primer estándar C ++ que es compatible con C99), y debe definirse en el archivo de encabezado de
ascensos como máximo un gran número que cabe en un objeto de tipo
long long int .
Climits también tiene algunas definiciones de macro más (para el número más pequeño posible y equivalentes sin signo), pero decidí usar esta macro, que no es importante. Lo importante es que, a diferencia de boost, en esta implementación los tipos "grandes" de C no se definirán como constantes enteras, aunque estén (posiblemente) presentes en el compilador. Lo que es más importante son los tipos
char16_t y
char32_t , que también se introdujeron en C ++ 11, pero ya no se entregaron en C99 (aparecieron ya simultáneamente con C ++ en el estándar C11) y, por lo tanto, en los estándares antiguos, su definición puede solo a través de un alias de tipo (por ejemplo
typedef short char16_t , pero más sobre eso más adelante). Si es así, para que la especialización de plantilla maneje correctamente las situaciones en que estos tipos están separados (integrados) y cuando se definen a través de
typedef ,
se necesita una capa más de
detalles de especialización de plantilla
:: _ is_integral .
Un hecho interesante es que en algunos compiladores antiguos estos tipos "grandes" C-shy no son constantes integrales . Lo que se puede entender e incluso perdonar, ya que estos tipos no son estándar para C ++ hasta 11 estándares, y en general no deberían estar allí. Pero lo que es difícil de entender es que estos tipos en el último compilador de C ++ de la creatividad Embarcadero (Embarcadero C ++ Builder), que C ++ 11 supuestamente admite, todavía no son constantes en sus ensambles de 32 bits (como hace 20 años entonces era Borland todavía cierto). Aparentemente debido a esto, incluyendo, la mayoría de la biblioteca estándar de C ++ 11 falta en estos ensambles de 32 bits (#include ratio? Chrono? Will cost). Embarcadero parece haber decidido forzar la era de 64 bits con el lema: “¿Quieres C ++ 11 o un estándar más nuevo? ¡Cree un programa de 64 bits (y solo un sonido metálico, nuestro compilador no puede)!
Una vez finalizados los procedimientos con los tipos fundamentales de lenguaje, presentamos algunos patrones más simples:
Patrones simples template <bool, class _Tp = detail::void_type> struct enable_if { }; template <class _Tp> struct enable_if<true, _Tp> { typedef _Tp type; }; template<class, class> struct is_same : public false_type { }; template<class _Tp> struct is_same<_Tp, _Tp> : public true_type
Solo el hecho de que las plantillas se especializan para todos los modificadores del tipo (
volátil y
constante, por ejemplo) es notable aquí, porque algunos compiladores tienden a "perder" uno de los modificadores al expandir la plantilla.
Por separado, destaco la implementación de
is_signed y
is_unsigned :
namespace detail { template<bool> struct _sign_unsign_chooser; template<class _Tp> struct _signed_comparer { static const bool value = _Tp(-1) < _Tp(0); }; template<class _Tp> struct _unsigned_comparer { static const bool value = _Tp(0) < _Tp(-1); }; template<bool Val> struct _cat_base : integral_constant<bool, Val> {
Al implementar esta parte, entré en una batalla desigual con Borland C ++ Builder 6.0, que no quería hacer que estas dos plantillas heredaran de
integral_constant , lo que eventualmente resultó en docenas de errores internos del compilador "imitando" el comportamiento
integral_constant para estas plantillas. Aquí, tal vez, valga la pena seguir luchando y llegar a algún tipo de derivación complicada del tipo
is_ * un * firmado: integral_constant a través de plantillas, pero hasta ahora he pospuesto esta tarea como no una prioridad. Lo interesante de la sección de código anterior es cómo en el momento de la compilación se determina que el tipo no está firmado / firmado. Para empezar, todos los tipos no enteros están
marcados y para ellos la plantilla va a una rama especializada separada
_sign_unsign_chooser con el argumento de plantilla
false , que a su vez siempre devuelve
value == false para cualquier tipo, excepto los tipos de coma flotante estándar (siempre están firmados por razones obvias, entonces
_signed :: value será
verdadero ). Para los tipos enteros, simples, pero bastante entretenidos, se realizan verificaciones. Aquí usamos el hecho de que para los tipos enteros sin signo, cuando el número disminuye y luego pasa a un mínimo (obviamente 0), se produce un desbordamiento y el número adquiere su máximo valor posible.
Este hecho es bien conocido, así como el de ese tipo icónica de desbordamiento
es un comportamiento indefinido y para ello es necesario seguir (de acuerdo a la norma, no se puede reducir la variable
int menos de
INT_MIN y la esperanza de que, como resultado de desbordamiento llegar
INT_MAX, en lugar de 42 o disco duro formateado )
Escribimos
_Tp (-1) <_Tp (0) para verificar el tipo de "signo" utilizando este hecho, luego para los tipos sin signo -1 "se transforma" a través del desbordamiento al número máximo de este tipo, mientras que para los tipos con signo dicha comparación se realizará sin desbordamiento, y -1 se comparará con 0.
Y el último para hoy, pero lejos del último "truco" en mi biblioteca es la implementación de
alineación_de :
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 { static const std::size_t value = A < S ? A : S; }; template <unsigned A> struct _alignment_logic_helper<A, 0> { static const std::size_t value = A; }; template <unsigned S> struct _alignment_logic_helper<0, S> { static const std::size_t value = S; }; template< class _Tp > struct _alignment_of_impl { #if _MSC_VER > 1400
Microsoft nuevamente se destacó aquí con su Visual Studio, que incluso teniendo una macro incorporada no estándar __alignof incorporada , aún produce resultados incorrectos al usarlo.
Explicación del impulsoLos usuarios de Visual C ++ deben tener en cuenta que MSVC tiene diferentes definiciones de "alineación". Por ejemplo, considere el siguiente código:
typedef long long align_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);
En este código, aunque boost :: alinear_de <align_t> informa que align_t tiene una alineación de 8 bytes, la afirmación final fallará para una compilación de 32 bits porque a1 no está alineado en un límite de 8 bytes. Tenga en cuenta que si hubiéramos utilizado el MSVC intrínseco __alignof en lugar de boost :: alineación_of, aún obtendríamos el mismo resultado. De hecho, los requisitos de alineación de MSVC (y las promesas) solo se aplican realmente al almacenamiento dinámico, y no a la pila.
Permítame recordarle lo que debe hacer la plantilla
std ::ignment_of: devolver un valor que represente los requisitos para la colocación de un elemento de este tipo en la memoria. Un poco de distracción, entonces un elemento de cada tipo tiene algún tipo de asignación de memoria, y si es continuo para una matriz de elementos, entonces, por ejemplo, las clases pueden tener "agujeros" entre los elementos miembros de la clase (
sizeof class
struct { char a;} probablemente no sea igual a 1, aunque hay 1 byte de todo dentro, porque el compilador lo alineará a 1 + 3 bytes durante el proceso de optimización).
Ahora veamos el código nuevamente. Declaramos la estructura
_alignment_of_trick , en la que colocamos un elemento del tipo que se verifica con una "sangría" en la memoria de 1 byte. Y verifique la alineación simplemente restando el tamaño del tipo que se verifica del tamaño de la estructura resultante. Es decir, si el compilador decide "pegar" un espacio vacío entre el elemento del tipo que se está verificando y el
carácter anterior, entonces obtenemos el valor de alineación de tipo en la estructura.
También aquí la aserción estática se encuentra primero como un tipo. Se declaran como:
namespace intern {
De hecho, estas plantillas especializadas son necesarias para reemplazar el
static_assert de C ++ 11, que se encuentra dentro de la definición de clase. Tal aserción carácter más ligero y propietaria de la
static_assert general de ejecución del
capítulo 2 , y no dejar que tirar de un archivo de cabecera en
type_traits core.h.
¿Muchos patrones? Habrá más! Nos detendremos en esto por ahora, a medida que continúe la fascinante historia sobre la combinación de la programación de plantillas con la tecnología SFINAE, así como por qué tuve que escribir un pequeño generador de código.
Gracias por su atencion