Skip to content

Instantly share code, notes, and snippets.

@MangaD
Created August 2, 2026 15:24
Show Gist options
  • Select an option

  • Save MangaD/3243f785ca51b4484667dbe0fc01017e to your computer and use it in GitHub Desktop.

Select an option

Save MangaD/3243f785ca51b4484667dbe0fc01017e to your computer and use it in GitHub Desktop.
The Rules of `decltype` in C++

The Rules of decltype in C++

CC0

Disclaimer: ChatGPT generated document.

decltype in C++ has a surprisingly small set of rules, but one special case makes it look complicated.

The key is to separate two rule sets.

1. decltype(name) — special rule

If the expression is an unparenthesized name (technically an unparenthesized id-expression or class member access), decltype gives you the declared type of that entity.

int x = 10;
const int cx = 20;

decltype(x)  a;   // int
decltype(cx) b;   // const int

References are preserved:

int x = 10;
int& r = x;

decltype(r) a = x;   // int&

Essentially:

If you give decltype a plain variable name, ask: "What type was this variable declared as?"

int        x;
const int  y;
int&       z = x;
int&&      w = 10;

decltype(x)  // int
decltype(y)  // const int
decltype(z)  // int&
decltype(w)  // int&&

Notice the last one:

int&& w = 10;

decltype(w) // int&&

Even though the expression w is an lvalue, the special decltype(name) rule looks at its declared type.


2. decltype(expression) — value-category rule

For other expressions, decltype examines the expression's type and value category.

Suppose the expression has type T.

Expression category decltype(expression)
lvalue T&
xvalue T&&
prvalue T

This table is the most important thing to memorize.

Lvalue → T&

int x = 10;

decltype((x))   // int&

Why?

(x) is no longer an unparenthesized variable name, so rule #1 doesn't apply.

Now C++ asks:

What is the value category of (x)?

It's an lvalue of type int.

Therefore:

decltype((x))
//       ^^^
// lvalue of type int

// → int&

This gives us the famous difference:

decltype(x)    // int
decltype((x))  // int&

Prvalue → T

For example:

decltype(42)       // int
decltype(x + 1)    // int

42 is a prvalue of type int, so:

prvalue int
    ↓
   int

No reference gets added.


Xvalue → T&&

For example:

int x;

decltype(std::move(x))   // int&&

std::move(x) produces an xvalue of type int.

Therefore:

xvalue int
    ↓
  int&&

The whole algorithm

When you see:

decltype(expr)

you can mentally do this:

              decltype(expr)
                    │
                    ▼
       Is expr an unparenthesized
       variable/member name?
              /           \
            YES            NO
             │              │
             ▼              ▼
      declared type     value category?
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
            lvalue        xvalue       prvalue
              │            │            │
              ▼            ▼            ▼
             T&           T&&            T

For example:

int x = 10;

decltype(x)             // int
decltype((x))           // int&
decltype(x + 1)         // int
decltype(std::move(x))  // int&&

const interacts naturally with these rules

Suppose:

const int x = 10;

Then:

decltype(x)     // const int

because the declared type is const int.

But:

decltype((x))   // const int&

because (x) is an lvalue whose type is const int.

And:

decltype(std::move(x))   // const int&&

because std::move(x) is an xvalue whose type is const int.

So:

expression           type/category          decltype
--------------------------------------------------------
x                    special case           const int
(x)                  const int lvalue       const int&
std::move(x)         const int xvalue       const int&&

This connects directly to your previous question about qualifiers: unlike many forms of type deduction, decltype is deliberately good at preserving const and reference information.

One additional rule worth learning after this is decltype(auto), because it applies these exact decltype rules to deduce a variable or function return type—and that's where the difference between return x; and return (x); can become very important.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment