Why use std::type_index instead of std::type_info*
Categories:
Why Use std::type_index Instead of std::type_info* in C++?

Explore the advantages of std::type_index over raw std::type_info* pointers for type identification and comparison in C++, focusing on its utility in associative containers and RTTI.
In C++, Runtime Type Information (RTTI) allows programs to retrieve information about the type of an object during program execution. The primary mechanism for this is std::type_info, accessible via the typeid operator. While typeid returns a const std::type_info&, directly using std::type_info* for comparisons or as keys in associative containers can lead to unexpected behavior. This is where std::type_index comes into play, offering a robust and reliable solution for type identification.
The Challenge with std::type_info*
The typeid operator returns a reference to a std::type_info object. When you take the address of this reference (e.g., &typeid(T)), you get a const std::type_info*. The crucial point is that std::type_info objects are not guaranteed to be unique across different translation units or even different invocations of typeid for the same type. While std::type_info::operator== correctly compares types, comparing std::type_info* pointers directly using == only checks if they point to the exact same memory location, not if they represent the same type. This makes std::type_info* unsuitable as a key in std::map or std::unordered_map because these containers rely on pointer equality for their default comparison/hashing, not the semantic type equality provided by std::type_info::operator==.
flowchart TD
A[typeid(T) returns const std::type_info&] --> B{Take address: const std::type_info*}
B --> C1["Pointer comparison (ptr1 == ptr2)"]
C1 -- "Checks memory address equality" --> D1[Unreliable for type identity]
B --> C2["std::type_info::operator== (\*ptr1 == \*ptr2)"]
C2 -- "Checks semantic type equality" --> D2[Reliable for type identity]
D1 --> E["Problem: std::map/unordered_map use pointer equality by default"]
D2 --> F["Solution: std::type_index provides value semantics"]
E --> FThe problem with std::type_info* for type identity.
Introducing std::type_index
std::type_index (introduced in C++11) is a lightweight wrapper around const std::type_info*. Its primary purpose is to provide value semantics for std::type_info objects, making them suitable for use in standard library containers that require strict weak ordering (like std::map) or hashing (like std::unordered_map).
std::type_index achieves this by:
- Storing a
const std::type_info*: It internally holds a pointer to thestd::type_infoobject. - Overloading comparison operators: It overloads
operator==,operator<, etc., to delegate to the correspondingstd::type_infomember functions (std::type_info::beforeandstd::type_info::operator==). This ensures that comparisons are based on the actual type, not the pointer value. - Providing a hash function: It has a specialized
std::hashforstd::type_index, which delegates tostd::type_info::hash_code(), making it usable as a key instd::unordered_map.
#include <iostream>
#include <typeinfo>
#include <typeindex>
#include <map>
#include <string>
class Base {};
class Derived : public Base {};
int main() {
// Problem with raw type_info* as map key
std::map<const std::type_info*, std::string> type_info_map_bad;
// This might work on some compilers/platforms, but is not guaranteed
// to correctly identify types across different typeid invocations or TUs.
// The default comparison for map uses pointer comparison.
type_info_map_bad[&typeid(int)] = "Integer";
type_info_map_bad[&typeid(double)] = "Double";
std::cout << "Bad map size: " << type_info_map_bad.size() << std::endl;
// Accessing might fail if &typeid(int) yields a different pointer
// even if it refers to the same type_info object.
// Solution with std::type_index
std::map<std::type_index, std::string> type_info_map_good;
type_info_map_good[std::type_index(typeid(int))] = "Integer";
type_info_map_good[std::type_index(typeid(double))] = "Double";
type_info_map_good[std::type_index(typeid(Base))] = "Base Class";
type_info_map_good[std::type_index(typeid(Derived))] = "Derived Class";
std::cout << "Good map size: " << type_info_map_good.size() << std::endl;
// Accessing elements using std::type_index is reliable
std::cout << "Type of int: " << type_info_map_good[std::type_index(typeid(int))] << std::endl;
std::cout << "Type of Derived: " << type_info_map_good[std::type_index(typeid(Derived))] << std::endl;
// Comparing type_info* vs type_index
const std::type_info* p_int1 = &typeid(int);
const std::type_info* p_int2 = &typeid(int);
std::cout << "\nComparing type_info*:\n";
std::cout << "p_int1 == p_int2: " << (p_int1 == p_int2 ? "true" : "false") << std::endl; // May be true or false
std::cout << "*p_int1 == *p_int2: " << (*p_int1 == *p_int2 ? "true" : "false") << std::endl; // Always true
std::type_index ti_int1 = typeid(int);
std::type_index ti_int2 = typeid(int);
std::cout << "\nComparing std::type_index:\n";
std::cout << "ti_int1 == ti_int2: " << (ti_int1 == ti_int2 ? "true" : "false") << std::endl; // Always true
return 0;
}
Demonstrating std::type_index for reliable type comparison and use in std::map.
std::type_index when you need to store, compare, or use std::type_info objects as keys in standard library containers like std::map or std::unordered_map. It provides the correct value semantics for type identity.Key Benefits of std::type_index
The primary benefits of using std::type_index are:
- Reliable Type Comparison: It guarantees that two
std::type_indexobjects compare equal if and only if they represent the same type, regardless of whether their underlyingstd::type_info*pointers are identical. - Container Compatibility: It provides the necessary comparison and hashing mechanisms to be used directly as keys in
std::map,std::unordered_map,std::set, andstd::unordered_setwithout requiring custom comparators or hash functions. - Value Semantics: It behaves like a value type, making it easier to reason about and pass around in your code, unlike raw pointers which often imply ownership or specific memory locations.
- Standard Library Integration: It's part of the C++ standard library (
<typeindex>), ensuring portability and consistency across different compilers and platforms.
classDiagram
class std_type_info {
+name() : const char*
+hash_code() : size_t
+operator==(const std_type_info&) : bool
+before(const std_type_info&) : bool
}
class std_type_index {
-const std_type_info* _ptr
+std_type_index(const std_type_info&)
+operator==(const std_type_index&) : bool
+operator<(const std_type_index&) : bool
+hash_code() : size_t
+name() : const char*
}
std_type_index "1" -- "1" std_type_info : "wraps pointer to"
std_type_index ..> std_hash : "specialized for"Class diagram illustrating std::type_index wrapping std::type_info.
std::type_index solves the problem of using std::type_info in containers, remember that RTTI itself has performance implications and should be used judiciously. For compile-time type dispatch, consider std::variant with std::visit or template-based solutions.