47 / 163 · C++14 · 9 min
The deduction boundaries of auto and decltype(auto)
auto lets the compiler deduce a static type; it does not introduce a dynamic type. Whether references and const are kept depends on the declaration form; decltype(auto) applies decltype rules directly, and parentheses can change the result, especially for function return values and lifetime.
In this lesson
Decide value versus borrow first
C++11 auto determines a type with rules similar to template argument deduction. auto x = source usually produces a new value: it drops the source object's top-level const and reference; auto& x = source declares a reference and keeps the referred object's const. const auto& is suitable for read-only borrowing, but you still must check the source object's lifetime.
This is not "automatically choosing the type that copies least." When the source expression returns a reference to a large object, bare auto may still copy; when the source type itself is a proxy, bare auto may copy the proxy while it continues to refer to underlying storage. Read a declaration by judging ownership intent first, then looking at the deduced concrete type.
Parentheses in decltype have meaning
For an unparenthesized variable name, decltype(name) yields that entity's declared type; for a general expression it follows value category: an lvalue yields T&, an xvalue yields T&&, and a prvalue yields T. So for an ordinary integer x, decltype(x) is int, while decltype((x)) is int&.
C++14 decltype(auto) hands the initializer or return expression directly to these rules. You cannot freely append & the way you can with auto. In the example, the two accessors differ only by one pair of parentheses, yet they return a copy of the integer versus a reference to the original integer; that difference should have a clear reason in the interface design.
Return deduction does not check lifetime for you
When wrapping an existing function and you must preserve its reference return value exactly, decltype(auto) is useful; when you only want an independent result, auto or an explicit value type is usually easier to review. Returning a parenthesized local variable may deduce a local reference that dangles as soon as the function exits; compiling successfully does not mean it is safe.
The example binds the reference only to an object that is still alive in main, and uses type assertions to explain each deduction. Another easy misread is braces: auto a = {1, 2} deduces initializer_list, not vector; under modern rules auto b{1} is int. Before choosing a brace form, decide whether you want a single value or a list.
Pitfalls
- decltype(auto) with return (local) keeps a reference to the local variable; you cannot casually parenthesize return to "unify style".
- Bare auto does not guarantee independence from source storage; for example, element access on vector<bool> may return a proxy. Convert explicitly to bool when you need an independent bool value.
Run an example
Minimum C++14 · complete program · Download .cpp
#include <cassert>
#include <iostream>
#include <type_traits>
struct Box { int value; };
decltype(auto) copy_value(Box& b) { return b.value; }
decltype(auto) borrow_value(Box& b) { return (b.value); }
int main() {
const int source = 3;
auto copy = source;
auto& ref = source;
static_assert(std::is_same<decltype(copy), int>::value, "value");
static_assert(std::is_same<decltype(ref), const int&>::value, "borrow");
Box b{4};
static_assert(std::is_same<decltype(copy_value(b)), int>::value, "copy return");
static_assert(std::is_same<decltype(borrow_value(b)), int&>::value, "reference return");
auto detached = copy_value(b);
borrow_value(b) = 9;
assert(detached == 4 && b.value == 9);
std::cout << detached << ' ' << b.value << '\n';
}
Compile locally
g++ -std=c++14 -Wall -Wextra -Wpedantic -pthread modern-auto.cpp -o example && ./exampleExpected result
4 9
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
If you change auto detached = copy_value(b) to decltype(auto) detached = borrow_value(b), what is detached at the end? How do you keep a numeric copy?
Show a reference answer
detached becomes int&, so after the assignment through borrow_value(b) it also observes 9. Write auto detached = borrow_value(b) or int detached = borrow_value(b) to copy the then-current value 4. The difference is in the caller's declaration, not in whether the function's signature contains the words "automatic deduction".
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.