Type Query Mechanisms
C++ provides several mechanisms to query the type of expressions at compile time. The primary ones include decltype, typeid, std::declval, and type traits from <type_traits>.
1. Type Specifier decltype
decltype(expr) yields the type of the expression expr without evaluating it. This makes it useful for examining the type of variables, function calls, or even complex expressions in a safe way during compilation.
Example:
int a = 5;
decltype(a) b = 10; // b is int
Used for data member:
struct S1 {
int x1;
decltype(x1) x2;
double x3;
decltype(x2 + x3) x4;
};
Used in function parameter list.
int x1 = 0;
decltype(x1) sum(decltype(x1) a1, decltype(a1) a2)
{
return a1 + a2;
}
auto x2 = sum(5, 10);
Note on Reference and const Preservation:
const int& r = a;
decltype(r) x = a; // x is const int&
The following code would fail:
template<class T>
auto return_ref(T& t)
{
return t;
}
int x1 = 0;
static_assert(
std::is_reference_v<decltype(return_ref(x1))>
);
The following would be OK:
template<class T>
auto return_ref(T& t)->decltype(t)
{
return t;
}
int x1 = 0;
static_assert(
std::is_reference_v<decltype(return_ref(x1))>
);
decltype preserves the exact type of the expression, including reference and cv-qualifiers.
2. Type Identification Operator typeid (Runtime)
typeid(expr) yields a reference to a std::type_info object representing the type of the expression. It is evaluated at runtime and is primarily useful when working with polymorphic types.
Example:
#include <iostream>
#include <typeinfo>
void printType(int x) {
std::cout << "Type: " << typeid(x).name() << '\n';
}
Note: When used on polymorphic types through a base pointer or reference, typeid reveals the dynamic type. Otherwise, it yields the static type.
Note
-
Return Value Lifetime
The return value oftypeidis a lvalue reference to aconst std::type_infoobject. Its lifetime is extended to the entire lifetime of the program — it is safe to store the reference or pointer. -
No Copy Constructor
std::type_infohas a deleted copy constructor, so it cannot be copied. Attempting to assign it directly as a value will result in a compilation error.auto t1 = typeid(int); // ❌ Error: copy constructor is deleted auto& t2 = typeid(int); // ✅ OK: t2 is a const std::type_info& auto* t3 = &typeid(int); // ✅ OK: t3 is a const std::type_info* -
CV-Qualifiers Ignored
typeidalways ignores const and volatile qualifiers when comparing types:const int ci = 42; bool same = (typeid(int) == typeid(ci)); // trueThis means,
typeid(T) == typeid(const T) == typeid(volatile T) == typeid(const volatile T)
3. Function Template std::declval<T>()
std::declval<T>() is a utility from <utility> that simulates an rvalue of type T in unevaluated contexts. It is primarily used in conjunction with decltype to query types that depend on operations without needing an actual object of type T.
Example:
#include <utility>
/*
This works even if T has no default constructor, because the expression is unevaluated
— std::declval<T>() just returns a value of type T&& without constructing anything.
*/
template <typename T>
auto getReturnType() -> decltype(std::declval<T>().foo());
/*
This would be invalid because:
- T is a type, and T.foo() is not a valid syntax (you can't call .foo() on a type).
- There's no instance of T to call foo() on.
- Even if T had a static member function foo(), that would be accessed as T::foo().
*/
template <typename T>
auto getReturnType() -> decltype(T.foo()); // ❌ Error
This technique is common in SFINAE and type trait definitions.
| Expression | Works? | Reason |
|---|---|---|
decltype(std::declval<T>().foo()) | ✅ | Simulates an rvalue of type T in unevaluated context |
decltype(T.foo()) | ❌ | Invalid syntax: T is a type, not an object |
decltype(T::foo()) | ✅ (only if foo() is static) | Accesses static member function |
4. Type Traits (<type_traits>)
The C++ standard library provides a wide range of type traits in the <type_traits> header for compile-time type inspection and transformation.
Examples:
#include <type_traits>
std::is_integral<int>::value // true
std::is_same<int, long>::value // false
std::remove_reference<int&>::type // int
std::decay<const int&>::type // int
Type traits enable generic code to adapt behavior based on type properties or to transform types as needed.
Summary
| Mechanism | Compile-Time | Runtime | Key Use Cases |
|---|---|---|---|
decltype(expr) | ✅ | ❌ | Exact type inference of expressions |
typeid(expr) | ❌ | ✅ | Runtime type information, polymorphism |
std::declval<T>() | ✅ | ❌ | Simulated expressions in decltype |
| Type Traits | ✅ | ❌ | Type inspection, manipulation, SFINAE |