C++ 9
For a local variable
T x;, what doesdecltype((x)) y = x;declare, and why is it a classic interview gotcha?Answer: It declares an lvalue reference to
T, binding directly toxwith no copy
This question tests a special rule of decltype: adding parentheses can change the resulting type.
Consider:
T x;
decltype((x)) y = x;The result is:
T& y = x;So y is an lvalue reference bound directly to x. No new T object is copied.
The gotcha is that these two expressions produce different types:
decltype(x) // T
decltype((x)) // T&Why?
decltype has two sets of rules.
When its argument is an unparenthesized variable name, such as:
decltype(x)C++ uses a special rule: the result is the declared type of x.
If:
T x;then:
decltype(x) // TTherefore:
decltype(x) y = x;is equivalent to:
T y = x; // copyy is a separate object.
But once you write:
decltype((x))the extra parentheses mean the special “variable name” rule no longer applies. Now decltype examines the value category of the expression (x).
Get W’s stories in your inbox
Join Medium for free to get updates from this writer.
A named variable is an lvalue expression, and the general decltype rule is:
expression is lvalue → T&
expression is xvalue → T&&
expression is prvalue → TSince:
(x)is an lvalue expression of type T:
decltype((x)) // T&Therefore:
decltype((x)) y = x;means:
T& y = x;You can see the difference by modifying y
For example:
int x = 10;
decltype((x)) y = x; // int& y = x
y = 99;
std::cout << x; // 99The relationship is:
x
┌──────────┐
│ 10 │
└──────────┘
▲
│
yy is simply another name for x.
Compare that with:
int x = 10;
decltype(x) y = x; // int y = x
y = 99;
std::cout << x; // 10Now there are two independent objects:
x y
┌──────┐ ┌──────┐
│ 10 │ │ 99 │
└──────┘ └──────┘Why is this an interview gotcha?
Because intuitively:
xand:
(x)appear to be exactly the same expression. In almost every ordinary context, adding those parentheses changes nothing.
But decltype deliberately distinguishes them:
T x;
decltype(x) // T
decltype((x)) // T&This becomes especially important in generic code. For example:
auto f() -> decltype(auto) {
int x = 42;
return (x); // dangerous!
}Because the returned expression is (x):
decltype((x)) // int&so decltype(auto) deduces the return type as:
int&But x is local and is destroyed when f() returns, so the function returns a dangling reference.
The key rule worth memorizing is:
decltype(name)
→ declared type of name
decltype((expression))
→ inspect value category:
lvalue → T&
xvalue → T&&
prvalue → TSo for this question:
T x;
decltype((x)) y = x;(x) is an lvalue, therefore decltype((x)) is T&, and y binds directly to x without making a copy.


