std::type_index logra un ordenamiento entre unidades de traducción encapsulando un puntero a un objeto std::type_info y delegando la comparación a la función de miembro subyacente before(). El ABI de C++ exige que el enlazador fusione la información de tipo para tipos idénticos a través de unidades de traducción en un único objeto canónico (usando secciones COMDAT o símbolos débiles), o asegure que before() proporcione un orden total consistente sin importar las diferencias de direcciones físicas. En consecuencia, std::type_index simplemente envuelve esta comparación garantizada por el ABI, proporcionando soporte para operator< y hash sin requerir que los tipos sean completos en el momento de la comparación. Este mecanismo depende enteramente de que la información de tipo en tiempo de ejecución (RTTI) esté habilitada, ya que el compilador debe emitir los metadatos de tipo necesarios para que el enlazador pueda desduplicar o reconciliar identidades de tipo a través de los límites de las bibliotecas compartidas.
Mientras diseñábamos una arquitectura de plugin para un motor de juegos, necesitábamos un registro central que mapease tipos de componentes a funciones de fábrica. Cada plugin (biblioteca compartida) registraba sus componentes utilizando typeid(Component).name() como la clave. Sin embargo, durante la prueba cruzada entre plataformas, descubrimos que las búsquedas con std::map fallaban intermitentemente cuando un plugin cargado en una biblioteca compartida intentaba recuperar una fábrica registrada por el motor central que residía en otra. La causa raíz fue que los nombres de cadena devueltos por type_info::name() diferían entre compiladores (GCC vs Clang), y la comparación directa de punteros de objetos type_info fallaba porque cada biblioteca compartida contenía instancias estáticas distintas para el mismo tipo.
Solución 1: Normalización de cadenas manual
Consideramos desmanglar y normalizar las cadenas type_info::name() utilizando APIs específicas del compilador como abi::__cxa_demangle para crear una clave canónica. Este enfoque prometía identificadores legibles para humanos adecuados para la depuración.
Pros: Las claves legibles para humanos facilitan el registro y la serialización.
Contras: Desmanglar es costoso, la comparación de cadenas es más lenta que la comparación de enteros, y el formato sigue siendo definido por la implementación, arriesgando que futuras actualizaciones del compilador rompan el registro.
Solución 2: Herencia virtual y RTTI personalizado
Exploramos requerir que todos los componentes hereden de una clase base que proporcione un método virtual GetTypeID() que devuelva una constante entera asignada manualmente.
Pros: Comparaciones deterministas y rápidas de enteros y sin dependencia del RTTI del compilador.
Contras: La asignación manual de ID es propensa a errores (colisiones), requiere modificar jerarquías de clases y no puede manejar tipos de terceros que no están bajo nuestro control.
Solución 3: Adopción de std::type_index
Refactorizamos el registro para usar std::map<std::type_index, FactoryFunc>, utilizando std::type_index(typeid(T)) como la clave.
Pros: El estándar garantiza un orden y hashing consistentes entre unidades de traducción a través de la comparación de type_info conforme al ABI, requiere ninguna gestión manual de ID y se integra a la perfección con el código existente que usa typeid.
Contras: Requiere que RTTI esté habilitado (aumentando el tamaño binario) y los objetos type_index no pueden ser serializados para transmisión en red o almacenamiento persistente.
Seleccionamos Solución 3 porque la fiabilidad de la identificación de tipos entre bibliotecas superó el costo del tamaño binario de RTTI. El comportamiento mandado por el estándar de std::type_index eliminó el frágil análisis de cadenas y el mantenimiento manual de ID que plagaron las alternativas.
El registro funcionó correctamente a través de los límites de DLL en Linux, Windows y macOS. Las búsquedas de fábricas se convirtieron en comparaciones O(log N) de punteros internos en lugar de operaciones de cadenas, reduciendo la latencia en la instanciación de componentes en aproximadamente un 40% en comparación con el enfoque de desmanglado. El sistema ahora soporta la recarga en caliente de plugins sin volver a registrar tipos del motor central.
std::type_info::name() devuelve una cadena de bytes nula terminada definida por la implementación; el estándar de C++ explícitamente declina especificar su formato, codificación o estabilidad. Por ejemplo, GCC generalmente devuelve nombres mangled (por ejemplo, "St6vectorIiSaIiEE"), mientras que MSVC devuelve nombres legibles para humanos (por ejemplo, "class std::vector<int,class std::allocator<int> >"). Los proveedores de compiladores pueden cambiar estas representaciones en futuras versiones para mejorar la depuración o reducir la longitud de los símbolos. En consecuencia, serializar estas cadenas en disco o protocolos de red crea un comportamiento indefinido al actualizar el compilador, ya que las claves guardadas anteriormente ya no coinciden con las recién generadas. Los candidatos a menudo suponen erróneamente que name() se comporta como un UUID estable.
Cuando el RTTI está deshabilitado, el compilador no emite objetos type_info para tipos polimórficos, y el operador typeid se vuelve mal formado (excepto para expresiones de tipo estático, que devuelven información de tipo estática en algunas implementaciones, pero generalmente está deshabilitado). std::type_index requiere un const std::type_info& para la construcción, y sin RTTI, los metadatos de tipo necesarios no existen en el binario. Debido a que esta es una dependencia en tiempo de compilación de los metadatos generados, el compilador emite un error (por ejemplo, "referencia indefinida a typeinfo for X") durante el enlazado en lugar de recurrir a una excepción de tiempo de ejecución capturable. Los candidatos a menudo esperan un std::bad_typeid en tiempo de ejecución o similar, confundiéndolo con fallos en dynamic_cast.
std::type_index almacena un puntero (o referencia) a un objeto std::type_info internamente. Los parámetros de plantilla no tipo en C++20 y anteriores requieren tipos estructurales donde todos los miembros son públicos y de tipos estructurales (o arreglos de ellos), y no pueden contener punteros a objetos con almacenamiento dinámico o direcciones dependientes del enlace. Dado que los objetos type_info residen en almacenamiento estático con direcciones dependientes del enlazador, y std::type_index no es un tipo estructural (tiene miembros privados y un constructor de copia no trivial en algunas implementaciones), no se puede usar como un NTTP. Aunque C++23 permite typeid en expresiones constantes, std::type_index en sí mismo sigue siendo no literal o no estructural en la mayoría de las implementaciones de bibliotecas estándar, impidiendo su uso en argumentos de plantilla donde se requieren constantes en tiempo de compilación.