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 --> F
The 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_info
object. - Overloading comparison operators: It overloads
operator==
,operator<
, etc., to delegate to the correspondingstd::type_info
member functions (std::type_info::before
andstd::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::hash
forstd::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_index
objects 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_set
without 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.