c++ - usar - stackoverflowespañol
¿Qué<type_traits> no se puede implementar sin ganchos de compilación? (2)
C ++ 11 proporciona <type_traits>
estándar.
¿Cuáles de ellos son imposibles de implementar sin los ganchos del compilador?
- Nota 1: por compilador me refiero a cualquier característica de lenguaje no estándar como
__is_builtin...
- Nota 2: muchos de ellos pueden implementarse sin enlaces (consulte el capítulo 2 de la Metaprogramación de plantillas C ++ y / o el capítulo 2 de Diseño moderno C ++ ).
- Nota 3: la respuesta de spraff en esta pregunta anterior cita a N2984 donde algunos rasgos de tipo contienen la siguiente nota: se cree que requiere soporte del compilador (gracias, seve).
He escrito una respuesta completa here : es un trabajo en progreso, por lo que estoy dando el enlace autoritativo a pesar de que estoy cortando y pegando el texto en esta respuesta.
Consulte también la documentación de libc ++ sobre el diseño intrínseco de rasgos de tipo .
is_union
is_union
consulta un atributo de la clase que no está expuesto por ningún otro medio; en C ++, cualquier cosa que puedas hacer con una clase o estructura, también puedes hacerlo con una unión. Esto incluye heredar y tomar punteros de miembro.
is_aggregate, is_literal_type, is_pod, is_standard_layout, has_virtual_destructor
Estos rasgos consultan los atributos de la clase que no están expuestos por ningún otro medio. Esencialmente, una estructura o clase es una "caja negra"; el lenguaje C ++ no nos permite abrirlo y examinar sus miembros de datos para averiguar si todos son tipos de POD, si alguno de ellos es private
o si la clase tiene algún constructor constexpr (el requisito clave para is_literal_type
) .
is_abstract
is_abstract
es un caso interesante. La característica definitoria de un tipo de clase abstracta es que no puede obtener un valor de ese tipo; por ejemplo, está 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()
. Es decir, es aceptable crear el tipo de una función con un tipo de retorno abstracto; no está bien definido para definir un entidad de tal tipo de función.)
Por lo tanto, podemos acercarnos mucho a una implementación correcta de is_abstract
utilizando 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 una falla! Si T
es en sí misma una clase de plantilla, como el vector<T>
o basic_ostream<char>
, entonces es aceptable simplemente formar el tipo T[]
; en un contexto no evaluado, esto no hará que el compilador ejemplifique el cuerpo de T
, y por lo tanto el compilador no detectará la forma incorrecta del tipo de matriz T[]
. Entonces, el SFINAE no sucederá en ese caso, y le daremos la respuesta incorrecta para is_abstract<basic_ostream<char>>
.
Esta peculiaridad de la creación de instancias de plantilla en contextos no evaluados es la única razón por la que los compiladores modernos proporcionan __is_abstract(T)
.
es_final
is_final
consulta un atributo de la clase que no está expuesto por ningún otro medio. Específicamente, la lista-especificador-base de una clase derivada no es un contexto SFINAE; no podemos explotar enable_if_t
para preguntar "¿puedo crear una clase derivada de T
?" porque si no podemos crear una clase así, será un error difícil.
esta vacio
is_empty
es un caso interesante. No podemos preguntarnos si sizeof (T) == 0
porque en C ++ no se permite que ningún tipo tenga el tamaño 0; incluso una clase vacía tiene sizeof (T) == 1
. Sin embargo, el "vacío" es lo suficientemente importante como para merecer un rasgo de tipo, debido a la optimización de la base vacía: todos los compiladores suficientemente modernos diseñarán las dos clases
struct Derived : public T { int x; };
struct Underived { int x; };
idénticamente es decir, no dispondrán espacio en Derived
para el subobjeto T
vacío. 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 "finalidad" de un tipo de clase no está expuesta por ningún otro medio. Así que los proveedores de compiladores que implementan final
también deben 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 rasgo de tipo observando que si nuestro tipo T
no es un tipo fundamental, un tipo de matriz, un tipo de puntero, un tipo de referencia, un puntero de miembro, una clase o unión, o un tipo de función, entonces por Proceso de eliminación debe ser un tipo de enumeración. Sin embargo, este razonamiento deductivo se descompone si el compilador soporta cualquier otro tipo que no pertenezca a las categorías anteriores. Por esta razón, los compiladores modernos exponen __is_enum(T)
.
Un ejemplo común de un tipo admitido que no cae en ninguna de las categorías anteriores sería __int128_t
. libc ++ realmente detecta la presencia de __int128_t
y lo incluye en la categoría de "tipos integrales" (lo que lo convierte en un "tipo fundamental" en la categorización anterior), pero nuestra implementación simple no lo hace.
Otro ejemplo sería vector int
, en compiladores compatibles con las extensiones vectoriales de Altivec; este tipo es más obvio "no es integral" pero también "no es nada más", y ciertamente no es un tipo de enumeración!
is_trivially_constructible, is_trivially_assignable
La trivialidad de la construcción, la asignación y la destrucción son atributos de la clase que no están expuestos por ningún otro medio. Tenga en cuenta que con esta base no necesitamos ninguna magia adicional para consultar la trivialidad de la construcción predeterminada , la construcción de copias , la asignación de movimientos , etc. En cambio, 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 incorporado no es __is_trivially_destructible(T)
sino más bien __has_trivial_destructor(T)
. ¡Además, resulta que el builtin se evalúa como true
incluso para un tipo de clase con un destructor eliminado! Así que primero tenemos que comprobar que el tipo es destructible; y luego, si lo es, podemos preguntar a la magia incorporada si ese destructor es realmente trivial.
tipo_ subyacente
El tipo subyacente de una enumeración no se expone por ningún otro medio. Puede acercarse tomando sizeof(T)
y comparándolo con los tamaños de todos los tipos conocidos, y solicitando la firmeza del tipo subyacente a través de T(-1) < T(0)
; pero ese enfoque aún no puede distinguir entre los tipos subyacentes int
y long
en plataformas donde esos tipos tienen el mismo ancho (ni entre long
y long long
en plataformas donde esos tipos tienen el mismo ancho).
Según la última documentación de impulso que también es un poco antigua, pero creo que todavía es válida
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 los 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án a su debido tiempo.
Las siguientes clases de rasgos siempre necesitan soporte del 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 rasgos no se pueden implementar de manera portátil en el lenguaje C ++, aunque en la práctica, las implementaciones 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 los anteriores:
is_class