
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 las partes anteriores de este capítulo, examinamos mi implementación de las plantillas básicas de la biblioteca estándar, y en esta parte hablaremos sobre la combinación de la técnica SFINAE con plantillas y un poco sobre la generación de código.
Enlace a GitHub con el resultado de hoy para impacientes y no lectores:
Los compromisos y las críticas constructivas son bienvenidos
Más plantillas de C ++ en cat.
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.3 Punteros y todo-todo-todo
En esta etapa, solo pude obtener información sobre si el tipo es una matriz para
std :: is_array y fue posible iniciar plantillas para punteros. La implementación también fue trivial, pero no sin supuestos.
Una especialización de plantilla simple para matrices de una longitud dada "captura" todos los tipos de matrices, sin embargo, el problema surge con el tipo incompleto
T [] (una matriz sin especificar la longitud). El hecho es que este tipo no está definido por algunos compiladores (C ++ Builder) cuando se especializa una plantilla, y todavía no he encontrado una solución universal aquí.
Después de que la biblioteca fue "enseñada" a definir tipos incorporados, alineación en la memoria de tipos, trabajar con modificadores de tipos y otras cosas básicas a través de plantillas en tiempo de compilación, llegó el momento de los punteros y las referencias.

En C ++, se pueden distinguir dos grupos de punteros: punteros a miembros de la clase y punteros a otros objetos. ¿Por qué es importante esta separación para una mayor implementación de la biblioteca estándar? El hecho es que los punteros a los miembros de la clase tienen una diferencia significativa de otros punteros por la presencia de
esto , es decir. puntero a un objeto de esta clase. Y, de manera estándar, los punteros a un miembro de la clase tienen una sintaxis separada para definir, son un tipo separado y no pueden representarse a través de un puntero regular. En la práctica, esto se traduce en el hecho de que el tamaño de un puntero a un miembro de la clase suele ser mayor que el tamaño de un puntero regular (que
== sizeof (void *) ), porque Para implementar funciones de miembro virtuales de la clase, así como para almacenar
este puntero, los compiladores generalmente implementan punteros a un miembro de clase como una estructura (lea
sobre las funciones y la
estructura virtuales). La forma de presentar punteros a los miembros de la clase se deja, según el estándar, a discreción del compilador, pero recordaremos esta diferencia de tamaño y presentación cuando consideremos más código.
Para definir un puntero regular a un objeto, escribiremos una plantilla simple
is_pointer , así como una plantilla
is_lvalue_reference para referencias de objeto (
dejamos de lado la referencia is_rvalue_reference, porque hasta el undécimo estándar no había operador
&& , así como toda la semántica de movimiento):
namespace detail { template<class> struct _is_pointer_helper : public false_type { }; template<class _Tp> struct _is_pointer_helper<_Tp*> : public true_type { }; }
Ya no hay nada fundamentalmente nuevo aquí, todo lo mismo se hizo en las partes anteriores de este capítulo. Continuemos definiendo punteros a objetos; ahora veamos punteros a funciones.
Es importante comprender que una función y una función miembro de una clase son entidades completamente diferentes según el estándar:
- El primer puntero será normal (un puntero a un objeto), el segundo tendrá un puntero a un miembro de la clase.
void (*func_ptr)(int);
- Puede crear un enlace al primero (enlace de objeto), pero no puede crear un segundo enlace.
void (&func_ref)(int);
Aquí solo mencionaré un poco sobre la generación de código. Como antes de C ++ 11 no había plantillas con un número variable de parámetros, todas las plantillas donde podría haber un número diferente de parámetros se
determinaron mediante la especialización de la plantilla principal con cualquier gran número de parámetros en la entrada y su inicialización por parámetros ficticios predeterminados. Lo mismo se aplica a las sobrecargas de funciones, como Tampoco hubo macros con un número variable de parámetros. Dado que escribir 60-70 líneas del mismo tipo de especializaciones de plantillas con sus manos, la sobrecarga de funciones es una tarea bastante aburrida e inútil, y también está cargada de la posibilidad de cometer un error. Escribí un generador simple de código para plantillas y sobrecargas de funciones para estos fines. Decidí limitarme a definir funciones a 24 parámetros y esto parece bastante engorroso en el código, pero simple y claro:
namespace detail { template <class R> struct _is_function_ptr_helper : false_type {}; template <class R > struct _is_function_ptr_helper<R(*)()> : true_type {}; template <class R > struct _is_function_ptr_helper<R(*)(...)> : true_type {}; template <class R, class T0> struct _is_function_ptr_helper<R(*)(T0)> : true_type {}; template <class R, class T0> struct _is_function_ptr_helper<R(*)(T0 ...)> : true_type {};
... template <class R, class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24> struct _is_function_ptr_helper<R(*)(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24)> : true_type {}; template <class R, class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24> struct _is_function_ptr_helper<R(*)(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24 ...)> : true_type {}; }
Definimos los tipos que nos son familiares del capítulo anterior para la técnica SFINAE:
namespace detail {
Algunas macros más por conveniencia namespace detail { #define _IS_MEM_FUN_PTR_CLR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) const volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) const volatile); #ifdef _STDEX_CDECL _no_type _STDEX_CDECL _is_mem_function_ptr(...); #define _IS_MEM_FUN_CDECL_PTR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) const volatile); #define _IS_MEM_FUN_STDCALL_PTR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) const volatile); #define _IS_MEM_FUN_FASTCALL_PTR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) const volatile); #else _no_type _is_mem_function_ptr(...); #define _IS_MEM_FUN_CDECL_PTR #define _IS_MEM_FUN_STDCALL_PTR #define _IS_MEM_FUN_FASTCALL_PTR #endif #define _IS_MEM_FUN_PTR \ _IS_MEM_FUN_PTR_CLR \ _IS_MEM_FUN_CDECL_PTR \ _IS_MEM_FUN_STDCALL_PTR \ _IS_MEM_FUN_FASTCALL_PTR }
Las macros se definen para que sea relativamente conveniente redefinir
TIPOS y
ARGS las defina como una lista de tipos y parámetros, luego sustituya la macro
_IS_MEM_FUN_PTR para generar definiciones para todos los tipos de funciones posibles por parte del preprocesador. También vale la pena prestar atención al hecho de que para los compiladores de Microsoft, los
acuerdos de llamadas (
__fastcall ,
__stdcall y
__cdecl ) también son
importantes , porque con diferentes convenciones, las funciones serán diferentes, aunque tengan el mismo conjunto de argumentos y valor de retorno. Como resultado, todo este grandioso diseño macro se usa de manera bastante compacta:
namespace detail { #define TYPES #define ARGS _IS_MEM_FUN_PTR #undef TYPES #undef ARGS #define TYPES , class T0 #define ARGS T0 _IS_MEM_FUN_PTR #undef TYPES #undef ARGS #define TYPES , class T0, class T1 #define ARGS T0, T1 _IS_MEM_FUN_PTR #undef TYPES #undef ARGS
... #define TYPES , class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24 #define ARGS T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24 _IS_MEM_FUN_PTR #undef TYPES #undef ARGS
Y ahora por lo que estaba escrito:
namespace detail { template <class _Tp, bool _IsRef> struct _is_mem_function_ptr_impl { static _Tp *p; static const bool value = (sizeof(_is_mem_function_ptr(_is_mem_function_ptr_impl::p)) == sizeof(_yes_type)); typedef typename integral_constant<bool, _is_mem_function_ptr_impl::value == bool(true)>::type type; }; template <class _Tp> struct _is_mem_function_ptr_impl<_Tp, true>: public false_type {}; template <class _Tp> struct _is_mem_function_ptr_helper: public _is_mem_function_ptr_impl<_Tp, is_reference<_Tp>::value>::type {}; template <class _Tp, bool _IsMemberFunctionPtr> struct _is_function_chooser_impl : public false_type { }; template <class _Tp> struct _is_function_chooser_impl<_Tp, false> : public _is_function_ptr_helper<_Tp*> { }; template<class _Tp, bool _IsRef = true> struct _is_function_chooser : public false_type { }; template <class _Tp> struct _is_function_chooser<_Tp, false> { static const bool value = _is_function_chooser_impl<_Tp, _is_mem_function_ptr_helper<_Tp>::value>::value; }; }
Para verificar si un tipo es una función miembro de una clase, primero se verifica para ver si el tipo es una referencia. Luego se crea un puntero de este tipo y se sustituye en la función de sonda. Utilizando la técnica SFINAE, el compilador selecciona la sobrecarga necesaria de las funciones de la sonda para dicho puntero y, en función del resultado de la comparación con
_yes_type, forma el resultado.
Basado en una verificación de una función miembro de una clase, se escribe una verificación de tipo sobre si pertenece al tipo de función. Verificamos si el tipo es de referencia, de lo contrario, buscamos una especialización adecuada de estructuras de sonda de plantilla para un puntero de este tipo, que será
true_type para cualquier puntero de función con hasta 24 parámetros.
Y ahora usamos el resultado para implementar
is_function . Aquí, por la misma razón que en la
parte anterior , no pude heredar esta estructura de
integral_constant , por lo que el comportamiento de
integral_constant se "simula".
Y para implementar
is_member_function_pointer todavía es más simple:
Además, en base a estos patrones, podemos determinar si el tipo es un miembro de la clase en principio:
namespace detail { template<class _Tp> struct _is_member_object_pointer_impl1 : public _not_< _or_<_is_function_ptr_helper<_Tp>, _is_mem_function_ptr_helper<_Tp> > >::type { }; template<class _Tp> struct _is_member_object_pointer_impl2 : public false_type { }; template<class _Tp, class _Cp> struct _is_member_object_pointer_impl2<_Tp _Cp::*> : public true_type { }; template<class _Tp> struct _is_member_object_pointer_helper: public _and_<_is_member_object_pointer_impl1<_Tp>, _is_member_object_pointer_impl2<_Tp> >::type {}; }
Usó operaciones lógicas 'y', 'o', 'no' en tipos de la primera parte namespace detail { struct void_type {};
Aquí usamos operaciones lógicas en tipos que, con la ayuda de la plantilla
condicional , eventualmente seleccionan el tipo de plantilla apropiado. La programación de plantillas en todo su esplendor, como resultado, en la etapa de compilación, ya tenemos información sobre si el tipo es miembro de la clase. ¡Muy "furioso", pero qué espectacular y efectivo!
Una programación de plantillas un poco más pura en los mismos elementos lógicos y tenemos
is_fundamental ,
is_compound , etc. Signos (esto me deleita, ¿pero tú?):
Un lector atento notará que la definición de is_enum está comentada. El hecho es que no encontré formas de distinguir enum de otros tipos, pero creo que esto es factible sin el uso de macros dependientes del compilador. Quizás un lector atento y conocedor le dirá su camino o tren de pensamiento al respecto.
Para determinar el hecho de que un tipo es una clase, ahora no se necesita nada más:
namespace detail { template <class _Tp, bool _IsReference> struct _is_class_helper { typedef integral_constant<bool, false> type; }; template <class _Tp> struct _is_class_helper<_Tp, false> { typedef integral_constant<bool, (is_scalar<_Tp>::value == bool(false))
Y todo estaría bien, pero la
unión en C ++ no se puede distinguir de una clase en el caso general. Debido a que son muy similares en sus "manifestaciones externas", y no pude verificar las diferencias (por ejemplo, la incapacidad de heredar de la
unión ) sin errores de compilación. Quizás alguien le dirá una maniobra difícil para determinar la
unión en la compilación, entonces
is_class se corresponderá exactamente con el estándar.
En la
parte final de este capítulo, hablaré sobre cómo
se implementaron std :: decay y
std :: common_type , así como sobre lo que queda por agregar a
type_traits .
Gracias por su atencion