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:
- Side Effects Are Ignored: Any function calls,
assignments, or increments inside the
typeidexpression are never executed at runtime. - Safe Dereferencing: Dereferencing a null or
dangling pointer inside
typeiddoes not cause undefined behavior or trigger runtime errors. Unlike polymorphic expressions—which throw astd::bad_typeidexception if a null pointer is dereferenced—a non-polymorphic dereference such astypeid(*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 totypeid(const int).typeid(int&)is identical totypeid(int).typeid(const int&)is identical totypeid(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.