Using typeid for Runtime Type Identification in C++

This article provides an overview of how the typeid operator enables runtime type identification (RTTI) in C++. It explains the distinction between static and dynamic type resolution, how compilers use virtual method tables (vtables) to retrieve runtime type metadata, and the practical requirements for correctly identifying derived classes through base class references or pointers.

In C++, typeid is an operator used to query the type of an expression at runtime or compile time. It yields a reference to a constant std::type_info object defined in the <typeinfo> header, which holds implementation-defined metadata about the type, including its name and equality comparison capabilities.

Static vs. Dynamic Resolution

The behavior of typeid depends fundamentally on whether the evaluated expression involves a polymorphic type:

  1. Non-Polymorphic Types: If an expression is a fundamental type, a non-class type, or a class without any virtual functions, typeid resolves the type purely at compile time. The compiler inspects the static type of the operand, discarding any dynamic evaluation.
  2. Polymorphic Types: If the operand is a dereferenced pointer or a reference to a class type that contains at least one virtual function, typeid defers evaluation to runtime. This allows it to determine the actual derived type of the object rather than the static type of the reference or pointer.

How typeid Works Under the Hood

Dynamic type identification relies on the compiler's implementation of Run-Time Type Information (RTTI), which is integrated into the class's virtual table infrastructure:

  • The Virtual Table Pointer (vptr): Every instance of a polymorphic class contains an implicit pointer (vptr) that points to that class's virtual table (vtable).
  • Type Information Metadata: Modern ABIs (such as the Itanium C++ ABI used by GCC and Clang, as well as MSVC's ABI) allocate a pointer to a std::type_info structure alongside the virtual function pointers inside each class's vtable—typically at a fixed negative offset relative to the function pointers.
  • Runtime Lookup: When typeid(*base_ptr) is executed, the program follows the object's vptr to its vtable, reads the associated std::type_info pointer, and returns a reference to that specific instance's runtime type metadata.

Practical Implementation

To inspect dynamic types correctly, the pointer must be dereferenced so that the operator inspects the object itself rather than the pointer variable:

#include <iostream>
#include <typeinfo>

class Base {
public:
    virtual ~Base() = default; // Declares the class as polymorphic
};

class Derived : public Base {};

int main() {
    Base* ptr = new Derived();

    // Querying the pointer yields static type "Base*"
    std::cout << typeid(ptr).name() << '\n';

    // Querying the dereferenced object yields dynamic type "Derived"
    std::cout << typeid(*ptr).name() << '\n';

    // Comparing types directly
    if (typeid(*ptr) == typeid(Derived)) {
        std::cout << "Object is an instance of Derived\n";
    }

    delete ptr;
    return 0;
}

Critical Considerations

  • Dereferencing Null Pointers: If typeid is applied to a dereferenced null pointer of a polymorphic type (e.g., typeid(*ptr) where ptr is nullptr), the C++ runtime throws a std::bad_typeid exception instead of causing undefined behavior.
  • Exact Matching vs. Inheritance: typeid checks for exact type identity. Unlike dynamic_cast, comparing typeid(*ptr) == typeid(Base) evaluates to false if *ptr is an instance of Derived, as it does not account for class hierarchy relationships.
  • Compiler Settings: RTTI can introduce a minor overhead in binary size and execution speed. Consequently, some environments allow disabling RTTI via compiler flags (such as -fno-rtti in GCC/Clang or /GR- in MSVC), which disables dynamic typeid evaluation.