How Decltype Inspects Types in C++
The decltype specifier in C++ inspects and extracts the
exact declared type of an expression or variable at compile time without
executing any code. This article explains how decltype
operates, detailing the distinction between bare identifiers and general
expressions, how value categories dictate reference generation, and how
compile-time type deduction is resolved in practice.
The Unevaluated Context
decltype operates strictly at compile time. Like
sizeof, the operand passed to decltype is an
unevaluated operand. The compiler analyzes the abstract syntax tree and
type attributes of the expression without generating runtime
instructions for evaluation.
int get_value();
decltype(get_value()) x; // x is of type int; get_value() is never called.Rule 1: Id-Expressions and Member Access
When the argument passed to decltype(e) is an
unparenthesized id-expression (a simple variable name) or a class member
access, decltype yields the exact declared type of that
entity.
int a = 10;
const int& b = a;
decltype(a) var1; // Type: int
decltype(b) var2 = a; // Type: const int&In this case, any const, volatile, or
reference qualifiers present on the original declaration are preserved
precisely as written.
Rule 2: General Expressions and Value Categories
If the argument to decltype(e) is an expression other
than an unparenthesized id-expression (including parenthesized variable
names), decltype determines the type based on the value
category of the expression:
- Lvalue: If
ehas typeTand is an lvalue,decltype(e)yieldsT&. - Xvalue: If
ehas typeTand is an xvalue (an expiring value, such as a moved temporary),decltype(e)yieldsT&&. - Prvalue: If
ehas typeTand is a prvalue (a pure rvalue, like a literal or temporary object),decltype(e)yieldsT.
struct Point { int x; };
Point p;
decltype(p.x) t1; // Id-expression: int
decltype((p.x)) t2 = p.x; // Parenthesized expression, lvalue: int&
decltype(10 + 5) t3; // Arithmetic expression, prvalue: int
decltype(std::move(p)) t4 = std::move(p); // Xvalue: Point&&The Parenthesis Distinction
The difference between decltype(variable) and
decltype((variable)) is a direct consequence of these
rules. Wrapping an identifier in parentheses turns an id-expression into
an lvalue expression:
int n = 0;
decltype(n) a = n; // int (declared type of n)
decltype((n)) b = n; // int& (parenthesized expression is an lvalue)Practical Application
with decltype(auto)
Introduced in C++14, decltype(auto) uses the exact
deduction rules of decltype instead of standard
auto template deduction rules (which typically strip
references and top-level const qualifiers). This ensures
perfect forwarding of return types in functions and templates where the
value category must be preserved:
template <typename Container>
decltype(auto) get_first(Container& c) {
return c[0]; // If operator[] returns a reference, decltype(auto) deduces T&
}