HOWTO · C++

C++ Get Variable Type

The article includes two different methods to find the type of a variable in C++. One of the methods is to use the typeid operator whereas the other one is the decltype operator.

On this page

This article explains how to determine a C++ expression’s type with two different tools. Use decltype when the type must be known during compilation, such as when declaring a related variable or type alias. Use typeid when a program needs runtime type information. They answer different questions, and the spelling returned by typeid(...).name() is not portable. For a focused introduction, see the typeid operator.

Choose compile-time or runtime type information

C++ provides both compile-time type deduction and runtime type information. The decltype specifier asks the compiler for a type according to the language’s rules. It is useful when another declaration should follow the type of an existing variable, function call, or expression.

typeid is a language operator and does not need to be declared by a header. It obtains a std::type_info object describing a type; include <typeinfo> when using the std::type_info interface, such as its name() member. That member returns an implementation-defined character sequence; the C++ standard does not require readable names such as int, or abbreviations such as i. The same program may therefore show different strings with different compilers.

These tools do not replace each other. decltype produces a type for compile-time use; typeid supports runtime type information and comparisons.

Use typeid to inspect runtime types

typeid itself needs no header declaration. Include <typeinfo> for the std::type_info interface used by these comparisons and by members such as name(). In this example, the comparisons check the type of an ordinary integer and the result of an arithmetic expression. The printed labels are chosen by the program, so they do not depend on a compiler’s spelling for a type.

#include <iostream>
#include <typeinfo>

int main() {
    int count = 7;
    double price = 2.5;

    std::cout << std::boolalpha;
    std::cout << "count is int: " << (typeid(count) == typeid(int)) << '\n';
    std::cout << "product is double: "
              << (typeid(count * price) == typeid(double)) << '\n';
}
count is int: true
product is double: true

For a polymorphic class, applying typeid to a glvalue that refers to an object reports the object’s dynamic type. A glvalue is an expression that identifies an existing object; a reference to a base class is a common example. The class must have at least one virtual function. For non-polymorphic types, and for expressions that are not qualifying glvalues, typeid reports the static type instead. Do not assume that typeid on every base-class expression reveals a derived type.

You can print typeid(value).name() while debugging, but treat the result as implementation-specific diagnostic text. Do not parse it or use it as a stable file format, user-facing label, or cross-compiler identifier.

Use decltype to determine compile-time types

The decltype specifier was introduced in C++11. It can derive a declared variable’s type, a function’s declared return type, or the type associated with an expression. Unlike auto, which deduces a type from an initializer, decltype follows specific rules based on the form of its operand.

For an unparenthesized name that names a variable, decltype(name) gives that variable’s declared type. For a function call, it gives the function’s declared return type. For most other expressions, the result depends on the expression’s value category: an lvalue produces an lvalue reference type, an xvalue produces an rvalue reference type, and a prvalue produces a non-reference type.

That distinction makes parentheses significant. If value is declared as int, then decltype(value) is int, while decltype((value)) is int&, because the parenthesized expression is an lvalue. This behavior is useful when a template or generic function needs to preserve whether an expression is a reference. It can also surprise code that expects every decltype expression to produce a plain value type.

A compact example of these rules is:

#include <type_traits>

double make_price();

int main() {
    int count = 7;
    decltype(count) another_count = count;
    decltype(make_price()) price = 2.5;
    decltype((count)) count_reference = count;

    static_assert(std::is_same_v<decltype(another_count), int>);
    static_assert(std::is_same_v<decltype(price), double>);
    static_assert(std::is_same_v<decltype(count_reference), int&>);
}

The declarations are checked by the compiler; make_price need not be defined here because the example only uses its declared return type and does not call it. If you use std::is_same_v, compile as C++17 or later. The core decltype rules are available from C++11; for a C++11 or C++14 check, use std::is_same<Actual, Expected>::value from <type_traits> instead.

Choose the right tool and understand its limits

Use decltype when a declaration needs a type at compile time, such as a type alias based on an expression or a variable that should track a function’s return type. Remember to check whether the operand is a bare name or a parenthesized expression, because that can change whether the result is a reference.

Use typeid when runtime type information or a type comparison is needed. It reports a dynamic type only for a polymorphic glvalue; otherwise it describes the static type. Its name() text is implementation-defined, so use equality comparisons between type_info objects for program logic instead of comparing name strings.

In short, choose decltype for compile-time type formation and typeid for runtime type information. The distinction, the polymorphic-glvalue rule, and the implementation-defined nature of name() are important when adapting either tool to production code.