A Brief Discussion on C++ auto
This English vesion is a translation of the original Chinese article. It is translated by ChatGPT w/o any human proofreading. If you find any mistakes, please let me know.
The auto
keyword has actually existed since the C language, where it was used to indicate that a variable had automatic storage duration, meaning that the variable is created when the block of code in which it is declared is entered and destroyed when that block is exited. However, since this is the default behavior for local variables, it was rarely used. Bjarne Stroustrup, the father of C++, cleverly repurposed this existing keyword in C++11 and gave it a new meaning: automatic type deduction. auto
is very useful when dealing with complex types (such as various iterators) and variables that are difficult or even impossible to write out precisely (such as lambdas).
Basic Syntax
The auto
keyword allows the compiler to automatically deduce the type of a variable based on the initialization expression. Here are some basic usage examples:
- Direct deduction from a value:
auto x = 3.14; // x is of type double auto y = 42; // y is of type int auto ; // z is of type int
- Deduction from an expression:
int ; auto x1 = ; // x1 : int
- Deduction involving cv-qualifiers, references, or pointers:
From the above examples, the type deduction rules ofconst auto& x2 = ; // x2 : const int& auto& x3 = ; // x3 : int& - Error, cannot bind reference to temporary object float& ; auto y1 = ; // y1 : float const auto& y2 = ; // y2 : const float& auto& y3 = ; // y3 : float& A* ; auto* z1 = ; // z1 : A* auto z2 = ; // z2 : A* auto* z3 = ; // Error, bar does not return a pointer type
auto
start to become apparent, which we will discuss in detail in the next section. It is also worth noting that const references can bind to temporary objects (const auto& x2 = foo()
), whereas non-const references cannot. Semantically, modifying a temporary object makes no sense; in terms of lifespan, allowing non-const references to bind to temporaries would result in dangling references after the temporary object is destroyed, whereas const references extend the temporary object's lifespan to match the reference's.1 - Multiple variable declarations:
In multiple variable declarations, the type of// Multiple variable declarations auto a = 1, *b = &a; // Valid, declarations are processed from left to right auto x = 1, *y = &x; // Also valid, declarations are processed from left to right auto m = 1, y = 3.14; // Error, types are not compatible
auto
is deduced from the first declaration, and subsequent declarations must be compatible. This is because declarations are processed from left to right. - Use in dynamic allocation:
This is an uncommon but valid syntax.auto* p = new auto; // Using auto in dynamic allocation
Type Deduction Rules
The code covered in this section can be found here.
The type deduction rules of auto
are actually consistent with the template parameter deduction rules. This means that, for:
void ;
; // Deduction process of T
auto x = expr; // Deduction process of the type of x
The process of deducing the template parameter T
in foo(expr)
follows the same rules as the process of deducing the type of x
in auto x = expr
. These rules can be divided into the following three cases:
-
Declaration of the form
auto x = expr
:- Discard reference qualification of
expr
- Discard top-level
const
qualifier ofexpr
- Arrays decay to pointers
- Functions decay to function pointers
const int a = 42; auto b = a; // b is of type int, const is discarded const int& c = a; auto d = c; // d is of type int, both const and reference are discarded int arr; auto e = arr; // e is of type int*, array decays to pointer void ; auto f = foo; // f is of type void(*)(), function decays to function pointer
- Discard reference qualification of
-
Declaration of the form
auto& x = expr
(reference deduction): It is obvious thatx
is of reference type, and the rules for deducing the type of the reference are:- Keep the
const
qualifier ofexpr
- If
expr
itself is a reference, use its underlying type - No array or function decay occurs
const int a = 42; auto& b = a; // b is of type const int& int& c = a; auto& d = c; // d is of type int& int arr; auto& e = arr; // e is of type int(&)[10] void ; auto& f = foo; // f is of type void(&)()
- Keep the
-
Declaration of the form
auto&& x = expr
(universal reference deduction)This case is a bit more complex. First of all,
auto&&
(orT&&
in template parameter deduction) is a universal reference, meaning it can match an lvalue or an rvalue. During type deduction:- If
expr
is an lvalue (assuming its type isparam_t
),auto
is deduced asparam_t&
(a left reference). - If
expr
is an rvalue,auto
is deduced as a non-reference type, i.e.,param_t
(following the same rules asauto& x = expr
). - After this, when the compiler substitutes the deduced type into
auto&&
orT&&
(imagine we get a type ofparam_t& &&
orparam_t&&
), reference collapsing occurs. The rule for reference collapsing is quite simple: if at least one of the two references is an lvalue reference, the result is an lvalue reference; otherwise, it is an rvalue reference. This means thatT&& &&
collapses toT&&
,T& &&
collapses toT&
,T&& &
collapses toT&
, andT& &
collapses toT&
(the latter two cases do not appear inauto&&
deduction).
int a = 42; auto&& b = a; // a is an lvalue, auto is deduced as int&, resulting in int& &&, which collapses to int& int&& rref = 42; // rref is an rvalue reference, but it itself is an lvalue auto&& r = rref; // auto is deduced as int&, resulting in int& &&, which collapses to int& auto&& c = 42; // 42 is an rvalue, auto is deduced as int, resulting in int&& auto&& d = ; // std::move(a) is an rvalue, auto is deduced as int, resulting in int&& &&, which collapses to int&&
- If
Further Reading
- Effective Modern C++ by Scott Meyers, Item 2: Understand auto type deduction.
- cppreference.com, auto specifier.
Footnotes
; // The temporary object is destroyed when the constructor ends
is incorrect. ↩