¿Qué no se puede implementar sin ganchos de compilador?
C++11 proporciona <type_traits>
estándar.
¿Cuáles de ellos son imposibles de implementar sin ganchos de compilador?
- Nota 1: por compilador hook me refiero a cualquier característica de lenguaje no estándar como
__is_builtin...
. - Nota 2: muchos de ellos se pueden implementar sin hooks (ver capítulo 2 de Metaprogramación de plantillas de C++ y/o capítulo 2 de Diseño Moderno de C++ ).
- Nota 3: Respuesta de spraff en esta pregunta anterior cita N2984 donde algunos rasgos de tipo contienen la siguiente nota: se cree que requieren soporte de compilador (gracias sehe).
2 answers
He escrito una respuesta completa aquí - es un trabajo en progreso, así que estoy dando el hipervínculo autorizado a pesar de que estoy cortando y pegando el texto en esta respuesta.
También vea la documentación de libc++sobre Características de tipo diseño intrínseco.
Is_union
is_union
consulta un atributo de la clase que no se expone a través de ningún otro medio;
en C++, todo lo que se puede hacer con una clase o estructura, también se puede hacer con una unión. Este
incluir heredar y tomar punteros de miembros.
Is_aggregate, is_literal_type, is_pod, is_standard_layout, has_virtual_destructor
Estos rasgos consultan atributos de la clase que no se exponen a través de ningún otro medio.
Esencialmente, una estructura o clase es una "caja negra"; el lenguaje C++ no nos da forma de
abrirlo y examinar sus miembros de datos para averiguar si son todos los tipos de POD, o si
cualquiera de ellos es private
, o si la clase tiene algún constructor constexpr (la clave
requisito para is_literal_type
).
Is_abstract
is_abstract
es un caso interesante. La característica definitoria de un resumen
el tipo de clase es que no puede obtener un valor de ese tipo; por ejemplo, es
mal formado para definir una función cuyo parámetro o tipo de retorno es abstracto, y
está mal formado para crear un tipo de matriz cuyo tipo de elemento es abstracto.
(Curiosamente, si T
es abstracto, entonces SFINAE se aplicará a T[]
pero no a T()
. Que
es, es aceptable crear el tipo de un función con un tipo de retorno abstracto;
está mal formado definir una entidad de tal tipo de función.)
Así que podemos acercarnos mucho a una correcta implementación de is_abstract
usando
este enfoque SFINAE:
template<class T, class> struct is_abstract_impl : true_type {};
template<class T> struct is_abstract_impl<T, void_t<T[]>> : false_type {};
template<class T> struct is_abstract : is_abstract_impl<remove_cv_t<T>, void> {};
Sin embargo, hay un defecto! Si T
es en sí misma una clase de plantilla, como vector<T>
o basic_ostream<char>
, entonces simplemente formar el tipo T[]
es aceptable; en
un contexto sin evaluar esto no hará que el compilador vaya a instanciar el
cuerpo de T
, y por lo tanto el compilador no detectará la falta de forma de
el tipo de matriz T[]
. Así que las SFINAE no sucederán en ese caso, y
dar la respuesta incorrecta para is_abstract<basic_ostream<char>>
.
Esta peculiaridad de la instanciación de plantillas en contextos no evaluados es la única razón
que los compiladores modernos proporcionan __is_abstract(T)
.
Is_final
is_final
consulta un atributo de la clase que no se expone a través de ningún otro medio.
Específicamente, la base-specifier-list de una clase derivada no es un SFINAE contexto; no podemos
explota enable_if_t
para preguntar "¿puedo crear una clase derivada de T
?"porque si
no se puede crear tal clase, será un error difícil.
Is_empty
is_empty
es un caso interesante. No podemos simplemente preguntar si sizeof (T) == 0
porque
en C++ no se permite que ningún tipo tenga tamaño 0; incluso una clase vacía tiene sizeof (T) == 1
.
"Vacío" es lo suficientemente importante como para merecer un rasgo de tipo, sin embargo, debido a la Base Vacía
Optimización: todos los compiladores suficientemente modernos trazarán los dos clases
struct Derived : public T { int x; };
struct Underived { int x; };
Idénticamente; es decir, no dispondrán ningún espacio en Derived
para el vacío
T
subobjeto. Esto sugiere una forma en que podríamos probar el "vacío" en C++03, al menos
en todos los compiladores suficientemente modernos: simplemente defina las dos clases anteriores y pregunte
si sizeof (Derived) == sizeof (Underived)
. Desafortunadamente, a partir de C++11, este
truco ya no funciona, porque T
podría ser final, y la "final -ess" de una clase
el tipo no está expuesto por ningún otro medio! Proveedores de compiladores que implementan final
también debe exponer algo como __is_empty(T)
para el beneficio de la biblioteca estándar.
Is_enum
is_enum
es otro caso interesante. Técnicamente, podríamos implementar este tipo de rasgo
por la observación de que si nuestro tipo T
es no un tipo fundamental, un tipo de matriz,
un tipo de puntero, un tipo de referencia, un puntero miembro, una clase o unión, o una función
tipo, luego por proceso de eliminación debe ser un tipo de enumeración. Sin embargo, este deductivo
el razonamiento se descompone si el compilador pasa a apoyar cualquier otro tipo de no caer
en las categorías anteriores. Por esta razón, los compiladores modernos exponen __is_enum(T)
.
Un ejemplo común de un tipo soportado que no cae en ninguna de las categorías anteriores
sería __int128_t
. libc++ realmente detecta la presencia de __int128_t
e incluye
en la categoría de "tipos integrales" (lo que lo convierte en un "tipo fundamental" en la anterior
categorización), pero nuestra simple implementación no lo hace.
Otro ejemplo sería vector int
, en compiladores compatibles con extensiones vectoriales de Altivec;
este tipo es más obviamente "no integral", sino también "no cualquier otra cosa", y la mayoría
ciertamente no es un tipo de enumeración!
Is_trivially_constructible, is_trivially_assignable
La trivialidad de la construcción, asignación y destrucción son todos atributos de la
clase que no están expuestos a través de ningún otro medio. Note que con esta fundación
no necesitamos ninguna magia adicional para consultar la trivialidad de default construcción,
copiar construcción, mover asignación, y así sucesivamente. En su lugar,
is_trivially_copy_constructible<T>
se implementa en términos de
is_trivially_constructible<T, const T&>
, y así sucesivamente.
Is_trivially_destructible
Por razones históricas, el nombre de este compilador builtin no es {[38]]}
sino más bien __has_trivial_destructor(T)
. Además, resulta que el builtin
evalúa a true
incluso para un tipo de clase con un destructor eliminado! Así que primero necesitamos
para comprobar que el tipo es destructible; y luego, si lo es, podemos preguntar al magia incorporada
si ese destructor es realmente trivial.
Underlying_type
El tipo subyacente de una enumeración no se expone a través de ningún otro medio. Puedes acercarte
tomando sizeof(T)
y comparándolo con los tamaños de todos los tipos conocidos, y preguntando
para la signedness del tipo subyacente vía T(-1) < T(0)
; pero ese enfoque todavía
no puede distinguir entre los tipos de subyacentes int
y long
en plataformas en las que
los tipos tienen el mismo ancho (ni entre long
y long long
en plataformas donde esos
los tipos tienen el mismo ancho).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-13 19:05:01
Per lastest boost documentation que también es un poco viejo pero creo que todavía es válido
Soporte para Intrínsecos del compilador
Hay algunos rasgos que no se pueden implementar dentro del lenguaje C++ actual: para hacer que estos rasgos "funcionen" con tipos definidos por el usuario, se requiere algún tipo de ayuda adicional del compilador. Actualmente (abril de 2008) Visual C++ 8 y 9, GNU GCC 4.3 y MWCW 9 proporcionan al menos algunos de los intrínsecos necesarios, y otros compiladores sin duda seguirá a su debido tiempo.
Las Siguientes clases de traits siempre necesitan soporte de compilador para hacer lo correcto para todos los tipos (pero todos tienen posiciones de reserva seguras si este soporte no está disponible):
is_union
is_pod
has_trivial_constructor
has_trivial_copy
has_trivial_move_constructor
has_trivial_assign
has_trivial_move_assign
has_trivial_destructor
has_nothrow_constructor
has_nothrow_copy
has_nothrow_assign
has_virtual_destructor
Las siguientes clases de traits no se pueden implementar portablemente en el lenguaje C++, aunque en la práctica, las implementaciones de hecho hacen lo correcto en todos los compiladores que conocemos:
is_empty
is_polymorphic
Las siguientes clases de rasgos dependen de uno o más de lo anterior:
is_class
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-24 22:52:08