<typeinfo>
The <typeinfo> header provides std::type_info and bad_typeid — C++'s built-in runtime type identification primitives used with the typeid operator.
std::type_infosince C++98std::type_info is a class holding implementation-defined type metadata produced by the typeid operator, supporting equality comparison, implementation-defined ordering, and (since C++11) hashing.
Overview
The <typeinfo> header is the foundation of C++'s runtime type identification (RTTI) system. It defines three entities:
std::type_info— the type descriptor returned bytypeidstd::bad_typeid— thrown whentypeidis applied through a null pointer to a polymorphic typestd::bad_cast— thrown bydynamic_castwhen a reference cast fails
The typeid operator has two operand forms with fundamentally different semantics:
Type operand (typeid(T)): always resolved at compile time. Returns a type_info describing T after stripping top-level references and cv-qualifiers.
Expression operand (typeid(expr)): if expr is a glvalue of polymorphic type (a class with at least one virtual function), the dynamic type is determined at runtime via the vtable. Otherwise, the expression is not evaluated and the result is the static type — the same behavior as the compile-time form.
This distinction is not subtle; it is the core of how typeid works. Applying typeid to a non-polymorphic expression never reveals the dynamic type. Only polymorphism triggers the runtime RTTI path.
type_info objects have pointer identity, not value identity. The standard guarantees exactly one type_info object per distinct type per program (subject to caveats around shared libraries). Objects are neither copyable nor default-constructible. References and pointers to type_info remain valid for the lifetime of the program.
C++11 added hash_code() to type_info and introduced std::type_index in a separate header <typeindex> as a copyable, hashable wrapper suitable for use in associative containers.
Syntax
#include <typeinfo>
// Compile-time type query — no expression evaluated
const std::type_info& ti = typeid(int);
// Runtime type query — requires polymorphic type
Base* p = new Derived{};
const std::type_info& dyn = typeid(*p); // resolves to Derived's type_info at runtime
// type_info interface (C++98)
bool same = (typeid(int) == typeid(long)); // false
bool ordered = typeid(int).before(typeid(long)); // implementation-defined ordering
const char* n = typeid(int).name(); // implementation-defined, often mangled
// C++11 additions
std::size_t h = typeid(int).hash_code(); // stable within one execution onlyDereferencing a null pointer of polymorphic type before typeid throws std::bad_typeid:
Base* null = nullptr;
typeid(*null); // throws std::bad_typeid (Base must be polymorphic)Examples
Type-safe dispatch in a type-erased container
type_info is the mechanism underlying std::any and many hand-rolled type-erasure containers. A minimal version illustrates the pattern:
#include <typeinfo>
#include <stdexcept>
#include <utility>
struct AnyBase {
virtual ~AnyBase() = default;
virtual const std::type_info& type() const noexcept = 0;
};
template<typename T>
struct AnyBox final : AnyBase {
T value;
explicit AnyBox(T v) : value(std::move(v)) {}
const std::type_info& type() const noexcept override { return typeid(T); }
};
template<typename T>
T& any_cast(AnyBase& box) {
if (box.type() != typeid(T))
throw std::bad_cast{};
return static_cast<AnyBox<T>&>(box).value;
}std::type_index for map keys (C++11)
type_info cannot be stored in an associative container: it is not copyable and before() does not map to std::less. std::type_index (<typeindex>, C++11) is a thin copyable wrapper satisfying both LessThanComparable and Hash:
#include <typeinfo>
#include <typeindex> // C++11
#include <unordered_map>
#include <functional>
using Handler = std::function<void(void*)>;
std::unordered_map<std::type_index, Handler> dispatch_table; // C++11
template<typename T>
void register_handler(Handler h) {
dispatch_table.emplace(std::type_index(typeid(T)), std::move(h));
}
void dispatch(const std::type_info& ti, void* data) {
auto it = dispatch_table.find(std::type_index(ti));
if (it != dispatch_table.end()) it->second(data);
}Compile-time vs runtime — a clarifying example
#include <typeinfo>
#include <print> // C++23
struct Animal { virtual ~Animal() = default; };
struct Dog : Animal {};
void identify(Animal& a) {
// Runtime: Animal is polymorphic, typeid dispatches via vtable
std::println("dynamic: {}", typeid(a).name()); // Dog's mangled name
Animal* p = &a;
// Compile-time: pointer type is Animal* — typeid does NOT dereference for you
std::println("pointer: {}", typeid(p).name()); // always Animal*
// Runtime: deref forces the runtime path
std::println("deref: {}", typeid(*p).name()); // Dog's mangled name
}Template introspection for debugging
#include <typeinfo>
template<typename T>
void debug_type(const T&) {
// Output is compiler-specific:
// GCC/Clang: mangled ABI names ("i", "PKc", "NSt7...")
// MSVC: decorated human-readable names ("int", "char const *")
// Never use name() in production logic — diagnostic only
std::puts(typeid(T).name());
}Best Practices
Prefer dynamic_cast over typeid equality for downcasting. typeid equality matches the exact dynamic type only; it will not match a more-derived type. dynamic_cast traverses the full inheritance graph and is the correct tool for hierarchy-aware casting. Reserve typeid for cases where you need to distinguish sibling types that dynamic_cast cannot differentiate.
Store type handles as std::type_index, not type_info*. Raw pointers to type_info are technically stable for the program's lifetime, but they are easy to dangle across dynamic library unload. std::type_index (C++11) wraps the pointer, satisfies Hash and LessThanComparable, and is the idiomatically correct key type for maps keyed by type.
Use name() only for diagnostics. The string returned is implementation-defined. GCC/Clang produce ABI-mangled output (PKc, NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE); MSVC produces decorated but differently formatted names. Never compare type names as strings in production logic — always compare type_info objects directly or via type_index.
Do not persist hash_code() across runs. Since C++11, hash_code() is guaranteed equal for identical types within a single execution, but the standard imposes no cross-run stability requirement. Serializing hash codes from type lookups produces non-portable, run-dependent data.
Common Pitfalls
RTTI disabled at compile time. GCC and Clang accept -fno-rtti; MSVC accepts /GR-. With RTTI disabled, typeid on a polymorphic expression is undefined behavior — some implementations throw, some silently return wrong data. If your codebase or a dependency uses these flags, every typeid call is suspect. Audit with -frtti or check __GXX_RTTI / _CPPRTTI.
Shared library type identity. On Itanium ABI platforms (Linux, macOS with GCC/Clang), type_info::operator== normally compares type_info pointer addresses first and falls back to name string comparison. This generally works across shared library boundaries — but only when the symbol for a given type_info object is not hidden. If a translation unit compiles with -fvisibility=hidden, a template instantiation can produce a private type_info object in each DSO, making operator== return false for the same logical type. Fix by explicitly marking exported types with __attribute__((visibility("default"))) or by keeping the RTTI-producing TU in a single shared library.
Pointer vs pointed-to object. typeid(ptr) evaluates to the static type of the pointer (T*), not the dynamic type of the pointee. Always dereference: typeid(*ptr). This is the single most common source of bugs when adding RTTI to existing code.
Reference types are invisible to typeid. typeid applied to a reference expression reports the referenced type, not the reference type itself — references have no runtime representation. You cannot use typeid to detect whether a value is a reference at runtime.
bad_typeid on non-polymorphic null dereference is UB, not a throw. The bad_typeid throw only applies when the operand is a glvalue of polymorphic type accessed through a null pointer. If the type has no virtual functions, the expression is not evaluated at all, so no exception is raised — but dereferencing null is still undefined behavior at the language level.
See Also
std::type_index(<typeindex>, C++11) — copyable, hashabletype_infowrapper suitable as a map or unordered_map keydynamic_cast— safe polymorphic downcast; prefer overtypeidequality checks for hierarchy traversalstd::any(<any>, C++17) — standard type-erased value container that usestype_infointernally for type-safe retrieval viastd::any::type()