typeid with Non-Polymorphic Types in C++

In C++, applying the typeid operator to an expression with a non-polymorphic type results in static, compile-time type resolution where the expression itself is completely unevaluated. Unlike its behavior with polymorphic types, typeid on non-polymorphic types does not inspect runtime metadata or virtual tables. This article explains how the C++ standard dictates the evaluation, cv-qualifier handling, and runtime safety of typeid when applied to non-polymorphic expressions.

Compile-Time Static Resolution

When an operand passed to typeid is not of a polymorphic class type (a class type containing at least one virtual function), the compiler determines the type entirely at compile time. The resulting std::type_info object represents the static type of the expression, regardless of the actual object that might be bound to it at runtime.

For example, if a base class pointer points to a derived class object, but the base class lacks virtual functions, typeid(*base_ptr) will evaluate to the base class type, not the derived type.

Unevaluated Operands

Because non-polymorphic types are resolved at compile time, the expression provided to typeid is an unevaluated operand, similar to the behavior of sizeof or decltype.

This has two critical implications:

  1. Side Effects Are Ignored: Any function calls, assignments, or increments inside the typeid expression are never executed at runtime.
  2. Safe Dereferencing: Dereferencing a null or dangling pointer inside typeid does not cause undefined behavior or trigger runtime errors. Unlike polymorphic expressions—which throw a std::bad_typeid exception if a null pointer is dereferenced—a non-polymorphic dereference such as typeid(*static_cast<int*>(nullptr)) is completely safe because the pointer is never actually accessed.

Stripping of cv-Qualifiers and References

When typeid receives an expression or type-id, it discards top-level const and volatile qualifiers, as well as reference qualifiers, before returning the std::type_info reference.

  • typeid(int) is identical to typeid(const int).
  • typeid(int&) is identical to typeid(int).
  • typeid(const int&) is identical to typeid(int).

However, cv-qualifiers that are not top-level—such as qualifiers on pointed-to types—are preserved. Therefore, typeid(int*) and typeid(const int*) produce distinct std::type_info representations.

Performance and Binary Footprint

Because non-polymorphic typeid operations do not require runtime type information (RTTI) lookups, they introduce zero runtime execution overhead. The compiler directly associates the expression with the corresponding static type metadata at compile time. Even if RTTI is disabled via compiler flags (such as -fno-rtti in GCC/Clang), non-polymorphic typeid queries often remain functional because they do not require runtime dynamic dispatch mechanisms.