Using std::type_index as Map Keys in C++
In modern C++, std::type_index serves as a wrapper
around std::type_info that allows runtime type information
to be used as keys in associative containers like std::map
and std::unordered_map. While the typeid
operator returns a reference to std::type_info, that object
cannot be used directly in standard containers because it is
non-copyable and lacks standard hashing support. This article explains
the limitations of std::type_info, how
std::type_index resolves these issues, and how to implement
type-indexed associative maps efficiently.
The Limitation of
std::type_info
When you query type information at runtime using
typeid(T), C++ returns a const reference to a
std::type_info object. However, std::type_info
cannot be stored directly as a key in a map due to specific language
design constraints:
- Non-Copyable and Non-Assignable: The copy
constructor and copy assignment operator of
std::type_infoare deleted, preventing map containers from storing value copies. - No Native Strict Weak Ordering: Standard
std::type_infoonly provides.before(), which cannot be used directly bystd::lesswithout custom comparators. - No Hash Support: There is no default
std::hash<std::type_info>specialization, preventing its direct use instd::unordered_map.
How
std::type_index Solves the Problem
Introduced in C++11 and defined in the <typeindex>
header, std::type_index is a lightweight, copyable wrapper
around a pointer to const std::type_info. It bridges the
gap between runtime type identification and the container requirements
by providing:
- Copyability and Assignability: Instances can be freely copied, moved, and assigned inside containers.
- Comparison Operators: It overloads
operator<,operator<=,operator>,operator>=,operator==, andoperator!=. The relational operators use the underlyingstd::type_info::before()method, satisfying the strict weak ordering requirement for ordered containers likestd::map. - Hash Specialization: The standard library provides
a specialization for
std::hash<std::type_index>, which delegates tostd::type_info::hash_code(). This enables out-of-the-box support forstd::unordered_map.
Implementation Example
The following example demonstrates using std::type_index
with both std::map and std::unordered_map to
map types to string descriptions or factory functions:
#include <iostream>
#include <typeindex>
#include <unordered_map>
#include <string>
#include <memory>
class Base { public: virtual ~Base() = default; };
class DerivedA : public Base {};
class DerivedB : public Base {};
int main() {
// std::unordered_map using std::type_index as the key
std::unordered_map<std::type_index, std::string> typeNames;
// Populating the map using typeid wrapped in std::type_index
typeNames[std::type_index(typeid(int))] = "Integer";
typeNames[std::type_index(typeid(double))] = "Double";
typeNames[std::type_index(typeid(DerivedA))] = "DerivedA Class";
// Querying with static types
std::cout << "int maps to: " << typeNames[std::type_index(typeid(int))] << '\n';
// Querying with dynamic polymorphic types
std::unique_ptr<Base> instance = std::make_unique<DerivedA>();
std::type_index runtimeType = std::type_index(typeid(*instance));
if (typeNames.find(runtimeType) != typeNames.end()) {
std::cout << "Dynamic instance type: " << typeNames[runtimeType] << '\n';
}
return 0;
}Common Use Cases
- Service Locators and Dependency Injection: Registering singletons or providers by their interface or concrete type.
- Heterogeneous Containers: Building storage structures capable of holding arbitrary types alongside their type metadata without manual enum tags.
- Event Dispatchers: Routing dynamic events to specific handler functions based on the dynamic event object's runtime type.