1. Problem It Solves
decltype(value) and decltype((value)) can produce different types. For an ordinary unparenthesized name, decltype uses the declared type; for other expressions, it also considers the expression's value category.
2. Prerequisites
T,T&,T&&, andconst.Lvalues such as named variables, xvalues such as
std::move(x), and prvalues such as42. Value category describes an expression, not just a variable's type.
3. Core Idea
For an ordinary variable int value, decltype(value) gives the declared type int. Parentheses in decltype((value)) select the expression rule: (value) is an lvalue, so the result is int&.
An unparenthesized member access such as object.member likewise uses the member's declared type. Structured bindings and constant template parameters have special rules. See the cases in the C++ draft.
4. Minimal Syntax
int value = 0;
decltype(value) copy = value;
decltype((value)) alias = value;
alias = 5;copy is a separate int that remains 0; alias is int&, so the assignment changes value to 5.
5. How It Works
For an expression e of type T outside the special name cases:
If
eis an lvalue,decltype(e)isT&.If
eis an xvalue,decltype(e)isT&&.If
eis a prvalue,decltype(e)isT.
The operand of decltype is unevaluated. For example, decltype(++value) is int& but does not increment value. The expression must still be well-formed.
A variable int&& r = 1; has declared type int&&, but the named expression r is an lvalue: decltype(r) is int&&, while decltype((r)) is int&.
6. Common Mistakes
Overlooking parentheses around a variable name.
Assuming that naming a variable of type
T&&always produces an xvalue.Using
decltype(auto)to return a reference to a local variable. Correct type deduction does not extend the object's lifetime.
7. When to Use It
Use decltype to obtain expression types in templates, declare return types, or check types with static_assert. decltype(auto) applies these rules when you need to preserve the initializer's value/reference behavior; check the referred object's lifetime.
8. Simple Example
The three central checks in the C++20 sample are:
int value = 0;
static_assert(std::is_same_v<decltype(value), int>);
static_assert(std::is_same_v<decltype((value)), int&>);
static_assert(std::is_same_v<decltype(std::move(value)), int&&>);These need <type_traits> and <utility>. The program compiles without printing anything. std::move(value) inside decltype is not evaluated.
Complete sample code
Source file
dailycppinterview/006_decltype/main.cpp
// Real-World C++ Interviews Q006: Explain decltype!
// Key: For an unparenthesized name or member-access expression, `decltype` generally reports
// the declared type of the named entity; structured bindings and constant template parameters
// have special rules. For other expressions of type `T`, it reflects value category: lvalue
// gives `T&`, xvalue gives `T&&`, and prvalue gives `T`. Thus `decltype(object.member)` gives
// the member's declared type, while `decltype((object.member))` gives an lvalue reference when
// `object` is an lvalue.
#include <type_traits>
#include <utility>
int main() {
int value = 0;
static_assert(std::is_same_v<decltype(value), int>);
static_assert(std::is_same_v<decltype((value)), int&>);
static_assert(std::is_same_v<decltype(std::move(value)), int&&>);
}
9. Key Takeaways
Read the name and parentheses first, then consider lvalue/xvalue/prvalue. decltype can produce a reference type even when you have not written & explicitly.
10. Self-Check Question
Full question: Explain decltype!
Given const int x = 3;, what are decltype(x), decltype((x)), and decltype(x + 1)? Does decltype(++value) increment value?