Question
In standard C++, is it possible to print the type of a variable by name?
For example:
int a = 12;
std::cout << typeof(a) << std::endl;
Expected output:
int
What is the standard C++ way to do this, and are there any limitations?
Short Answer
By the end of this page, you will understand how C++ handles type information at compile time and runtime, why there is no standard typeof operator that directly prints int, and how tools like typeid, type traits, and compiler-specific name demangling are commonly used instead.
Concept
C++ does not provide a standard feature that guarantees you can write code like this:
std::cout << typeof(a);
and get a clean human-readable type name such as int.
The core idea is that types in C++ are mainly a compile-time concept. The compiler uses type information to check correctness, choose overloads, generate code, and optimize programs. But once the program runs, that exact source-level type name is not always preserved in a portable, readable form.
What standard C++ does provide
Standard C++ provides runtime type information through typeid:
#include <iostream>
#include <typeinfo>
int main() {
int a = 12;
std::cout << typeid(a).name() << '\n';
}
This gives you a type name string, but there is an important limitation:
typeid(...).name()is implementation-defined- That means the output depends on the compiler
- It may print
int, or it may print something cryptic likei
So typeid is standard, but the exact printed name is not standardized.
Why this matters
Understanding this helps you avoid expecting reflection-like behavior from C++. In many languages, type names are easy to inspect at runtime. In C++, this is more limited because:
- performance and compile-time code generation are important
- type information is not designed primarily for runtime display
- readable type names are often compiler-specific
Compile-time vs runtime
There are two related but different problems:
-
Knowing a type in code
- Example: checking whether a type is
int - Usually done with templates or type traits
- Example: checking whether a type is
-
Printing a type name for debugging or logging
- Example: showing
inton screen - Often done with
typeid(...).name(), but portability is limited
- Example: showing
If your goal is program logic, prefer type traits.
If your goal is debugging, typeid may help, but the output may differ by compiler.
Mental Model
Think of a C++ type like a label on a package while it is being sorted in a warehouse.
- During sorting, the label is extremely important
- Workers use it to send the package to the right place
- But after shipping, the warehouse system may not keep the original label in a neat, human-friendly form
In the same way, the C++ compiler depends heavily on type information while building your program. But at runtime, you are not guaranteed to get the exact source-code name back as a clean string.
So:
- compiler stage = types are rich and important
- runtime stage = some type info may exist, but readable names are limited
Syntax and Examples
Standard approach with typeid
#include <iostream>
#include <typeinfo>
int main() {
int a = 12;
std::cout << typeid(a).name() << '\n';
}
What this does
typeid(a)returns astd::type_infoobject describing the type.name()returns a C-style string representing that type- The exact string is compiler-dependent
Example with multiple types
#include <iostream>
#include <string>
#include <typeinfo>
int main() {
int a = 12;
double b = 3.14;
std::string c = "hello";
std::cout << typeid(a).name() << '\n';
std::cout << typeid(b).name() << '\n';
std::cout << typeid(c).name() << '\n';
}
Depending on your compiler, the output might be readable or abbreviated.
Comparing types instead of printing them
If you want to check a type rather than print it, use type comparisons:
#include <iostream>
#include <typeinfo>
int main() {
int a = 12;
if (typeid(a) == typeid(int)) {
std::cout << "a is an int\n";
}
}
Better for compile-time logic: type traits
#include <iostream>
#include <type_traits>
int main() {
int a = 12;
if constexpr (std::is_same_v<decltype(a), int>) {
std::cout << "a has type int\n";
}
}
This is often the better solution when your goal is program behavior, not printing.
Important note about typeof
// Not standard C++
// typeof(a)
Some compilers historically provided typeof as an extension, but it is not part of standard C++.
The standard keyword that gets a type from an expression is:
decltype(a)
However, decltype(a) gives you a type in code, not a printable string.
Step by Step Execution
Consider this example:
#include <iostream>
#include <typeinfo>
int main() {
int a = 12;
std::cout << typeid(a).name() << '\n';
}
Step-by-step
1. int a = 12;
A variable named a is created with type int and value 12.
2. typeid(a)
The expression typeid(a) asks C++ for type information about a.
This produces a std::type_info object representing the type.
3. .name()
Calling .name() on that object returns a string-like C-style name for the type.
Important:
- the returned text is implementation-defined
- it might be
int - it might be a shorter encoded form such as
i
4. std::cout << ...
The returned name is printed to standard output.
Another traceable example
#include <iostream>
#include <typeinfo>
int main() {
int x = 5;
double y = 2.5;
std::cout << typeid(x).name() << '\n';
std::cout << typeid(y).name() << '\n';
}
Execution flow:
- Create
xasint - Create
yasdouble - Ask for type info of
x - Print its compiler-provided name
- Ask for type info of
y - Print its compiler-provided name
Real World Use Cases
Debugging template code
When working with templates, developers often want to inspect what type the compiler deduced:
template <typename T>
void printType(const T& value) {
std::cout << typeid(value).name() << '\n';
}
This can help during development, especially when generic code becomes hard to follow.
Logging unexpected values
In serialization, parsing, or generic utility code, you may log the type of a value when debugging behavior.
Verifying polymorphic behavior
With base-class references or pointers, typeid can help inspect the dynamic type when runtime type information is enabled and polymorphism is involved.
Unit testing generic utilities
Developers sometimes check that a helper produces the expected type using decltype and type traits instead of printing names.
static_assert(std::is_same_v<decltype(1 + 2), int>);
This is more reliable than printing type names.
Real Codebase Usage
In real C++ projects, developers usually do not rely on printing raw type names for core logic.
Common patterns
Guarding behavior with type traits
#include <type_traits>
template <typename T>
void process(const T& value) {
if constexpr (std::is_integral_v<T>) {
// handle integers
} else {
// handle other types
}
}
This is common in template-heavy code.
Using decltype for deduced types
auto x = 10;
decltype(x) y = 20;
Developers use this to keep types consistent without manually repeating them.
Debug-only type inspection
std::cerr << "Type: " << typeid(value).name() << '\n';
This is often used temporarily while debugging.
Validation and static checks
static_assert(std::is_same_v<decltype(std::declval<int>() + std::declval<int>()), int>);
This confirms type behavior at compile time.
Why this is preferred
- type traits are portable
- compile-time checks are safer
- logic based on type names as strings is fragile
- readable type printing often depends on compiler tools outside the standard
Common Mistakes
Mistake 1: Expecting typeof to be standard C++
int a = 12;
std::cout << typeof(a) << '\n';
This is not standard C++.
Fix
Use decltype for type deduction in code, or typeid for runtime inspection.
std::cout << typeid(a).name() << '\n';
Mistake 2: Assuming typeid(...).name() always prints int
int a = 12;
std::cout << typeid(a).name() << '\n';
You might expect:
int
But some compilers may print encoded names.
Fix
Treat .name() as useful for debugging, not as a portable user-facing string.
Mistake 3: Using printed type names in program logic
if (std::string(typeid(a).name()) == "int") {
// fragile
}
This is unreliable because the name is implementation-defined.
Fix
Use proper type comparisons:
if (typeid(a) == typeid(int)) {
// reliable comparison
}
Or use type traits:
if constexpr (std::is_same_v<decltype(a), int>) {
// compile-time check
}
Mistake 4: Confusing decltype with a printable value
std::cout << decltype(a) << '\n';
This is invalid because decltype(a) is a type, not a runtime value.
Fix
Use it in declarations or type traits:
decltype(a) b = 20;
Mistake 5: Forgetting the correct headers
typeidneeds#include <typeinfo>- type traits need
#include <type_traits>
Comparisons
| Feature | Standard C++ | Purpose | Output readability | Best use |
|---|---|---|---|---|
typeid(expr).name() | Yes | Runtime type inspection | Implementation-defined | Debugging |
typeid(expr) == typeid(T) | Yes | Runtime type comparison | Not applicable | Checking types at runtime |
decltype(expr) | Yes | Get the type of an expression in code | Not printable directly | Declarations, templates |
std::is_same_v<T, U> | Yes | Compare types at compile time | Not printable directly | Template logic |
typeof(expr) | No | Compiler extension in some environments | Varies | Avoid in portable code |
typeid vs decltype
typeid | decltype |
|---|---|
| Works at runtime | Works at compile time |
| Gives type information object | Gives a type in code |
Can be used with .name() | Cannot be printed directly |
| Useful for inspection | Useful for declarations and metaprogramming |
Which one should you use?
- Use
typeidwhen debugging or inspecting type information at runtime - Use
decltypewhen you need to refer to a deduced type in code - Use type traits when program behavior depends on a type
Cheat Sheet
Quick reference
Print a type name candidate
#include <typeinfo>
std::cout << typeid(x).name() << '\n';
- Standard C++: yes
- Exact string output: not portable
Compare a variable's type
if (typeid(x) == typeid(int)) {
// x is int
}
Get a type in code
decltype(x) y = x;
decltype(x)is a type, not a string
Compile-time type check
#include <type_traits>
if constexpr (std::is_same_v<decltype(x), int>) {
// x is int
}
Rules to remember
typeofis not standard C++typeid(...).name()is standard, but the returned text is implementation-defined- Use
decltypefor type deduction in declarations - Use
std::is_sameor other type traits for reliable type-based logic - Do not compare
.name()strings for portable code
Common headers
#include <typeinfo> // typeid
#include <type_traits> // std::is_same_v, std::is_integral_v
FAQ
Can I print int exactly in standard C++?
Not portably. typeid(x).name() is standard, but the exact text it returns is implementation-defined.
Is typeof valid in C++?
Not in standard C++. Some compilers support it as an extension, but portable C++ should not rely on it.
What should I use instead of typeof in standard C++?
Use decltype to refer to a type in code, and typeid if you need runtime type information.
Why does typeid(a).name() print i instead of int?
Because the format of the returned name depends on the compiler implementation. Some compilers return mangled or abbreviated names.
How do I check whether a variable is an int?
Use typeid(a) == typeid(int) at runtime, or std::is_same_v<decltype(a), int> at compile time.
Is decltype(a) the same as typeid(a)?
No. decltype(a) gives you a type in code at compile time. typeid(a) gives type information at runtime.
Should I use printed type names in application logic?
No. Use type comparisons or type traits instead. Printed names are not portable enough for logic.
Mini Project
Description
Build a small C++ program that inspects values of different types and shows how standard C++ can compare types safely, while also demonstrating the limits of printing human-readable type names. This helps reinforce the difference between runtime inspection and compile-time type checking.
Goal
Create a program that prints compiler-provided type names and reports whether each variable is an int using a safe standard approach.
Requirements
- Create variables of at least three different types
- Print each variable's type using
typeid(...).name() - Check whether each variable is an
int - Use standard C++ only
- Keep the output easy to read
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.