A comprehensive exploration of auto and decltype type deduction.
Compiler: GCC 11.4
C++ Standard: 201703
| Variable | Declared Type |
|---|---|
x |
int |
cx |
const int |
rx |
int& |
rcx |
const int& |
rrx |
int&& |
rrcx |
const int&& |
px |
int* |
pcx |
const int* |
cpx |
int* const |
cpcx |
const int* const |
When decltype is applied to an unparenthesized id-expression, it yields the declared type.
| Expression | Type |
|---|---|
decltype(x) |
int |
decltype(cx) |
const int |
decltype(rx) |
int& |
decltype(rcx) |
const int& |
decltype(rrx) |
int&& |
decltype(rrcx) |
const int&& |
When decltype is applied to a parenthesized expression (expr), it considers the value category:
- lvalue →
T& - xvalue →
T&& - prvalue →
T
| Expression | Type | Why |
|---|---|---|
decltype((x)) |
int& |
x is lvalue |
decltype((cx)) |
const int& |
cx is lvalue |
decltype((rx)) |
int& |
rx is lvalue |
decltype((rrx)) |
int& |
rrx is lvalue! |
decltype((std::move(x))) |
int&& |
xvalue |
decltype((42)) |
int |
prvalue |
decltype((get_value())) |
int |
prvalue |
decltype((get_lref())) |
int& |
lvalue |
decltype((get_rref())) |
int&& |
xvalue |
| Declaration | Deduced Type |
|---|---|
auto v = x |
int |
auto v = cx |
int |
auto v = rx |
int |
auto v = rcx |
int |
auto v = rrx |
int |
auto v = std::move(x) |
int |
auto v = get_value() |
int |
auto v = get_lref() |
int |
auto v = get_rref() |
int |
| Declaration | Deduced Type |
|---|---|
auto& v = x |
int& |
auto& v = cx |
const int& |
auto& v = rx |
int& |
auto& v = rcx |
const int& |
auto& v = rrx |
int& |
auto& v = get_lref() |
int& |
Note: auto& v = std::move(x), auto& v = get_value(), auto& v = get_rref() would be errors (can't bind lvalue ref to rvalue).
| Declaration | Deduced Type |
|---|---|
const auto& v = x |
const int& |
const auto& v = cx |
const int& |
const auto& v = rx |
const int& |
const auto& v = std::move(x) |
const int& |
const auto& v = get_value() |
const int& |
const auto& v = 42 |
const int& |
| Declaration | Deduced Type | Why |
|---|---|---|
auto&& v = x |
int& |
x is lvalue |
auto&& v = cx |
const int& |
cx is lvalue |
auto&& v = rx |
int& |
rx is lvalue |
auto&& v = rcx |
const int& |
rcx is lvalue |
auto&& v = rrx |
int& |
rrx is lvalue! |
auto&& v = std::move(x) |
int&& |
xvalue |
auto&& v = get_value() |
int&& |
prvalue |
auto&& v = get_lref() |
int& |
lvalue |
auto&& v = get_rref() |
int&& |
xvalue |
auto&& v = 42 |
int&& |
prvalue |
Preserves the exact type including references and cv-qualifiers.
| Declaration | Deduced Type | Why |
|---|---|---|
decltype(auto) v = x |
int |
id-expression → declared type |
decltype(auto) v = cx |
const int |
id-expression → declared type |
decltype(auto) v = std::move(x) |
int&& |
expression returns int&& |
decltype(auto) v = (x) |
int& |
(x) is lvalue → adds & |
decltype(auto) v = (cx) |
const int& |
(cx) is lvalue → adds const& |
Warning: decltype(auto) v = rrx; would be an error because rrx has declared type int&&, but rrx itself is an lvalue and can't bind to int&&.
| Declaration | Deduced Type | Note |
|---|---|---|
auto v = px |
int* |
|
auto v = pcx |
const int* |
pointee const preserved |
auto v = cpx |
int* |
top-level const stripped |
auto v = cpcx |
const int* |
top-level const stripped |
auto* v = px |
int* |
|
auto* v = pcx |
const int* |
pointee const preserved |
const auto* v = px |
const int* |
adds pointee const |
auto* const v = px |
int* const |
pointer is const |
expression
/ \
glvalue rvalue
/ \ / \
lvalue xvalue prvalue
| Category | Has Identity | Can Move From | Examples |
|---|---|---|---|
| lvalue | Yes | No | x, *p, a[n], ++i |
| xvalue | Yes | Yes | std::move(x), a[n] where a is rvalue |
| prvalue | No | Yes | 42, x + y, function returning by value |
autostrips top-level const and references — it always creates a new copyauto&preserves const but requires an lvalueauto&&is a forwarding reference — binds to anything, preserves value categorydecltype(var)returns declared type of the variabledecltype((var))adds reference based on value category (lvalue →T&)decltype(auto)uses decltype rules on the initializer expression- Named rvalue references are lvalues! —
rrxinint&& rrx = ...is an lvalue const auto&extends lifetime of temporariesauto*preserves pointee const butautostrips top-level pointer const
int&& rrx = std::move(x); // rrx has type int&&
auto&& v = rrx; // but rrx is an lvalue! v is int&int x = 42;
decltype(x) a; // int
decltype((x)) b = x; // int& (dangerous: returns reference to local!)const int cx = 42;
auto v = cx; // v is int, not const intGenerated by cpp-types-zoo