auto
Type deduction keyword that deduces variable types from initializers, function returns, and parameters. Strips top-level const/references unless explicitly marked.
autosince C++11Placeholder type that instructs the compiler to deduce the variable's or function's type from its initializer or return value, following template argument deduction rules.
Overview
auto eliminates redundant type declarations by letting the compiler infer types from context. It's one of the most impactful features of modern C++, enabling more maintainable code that adapts when right-hand sides change. The deduction mechanism follows the same rules as template argument deduction, making it predictable once you understand the pattern.
The key insight: auto strips top-level const and references. Add them back explicitly when you need them—const auto&, auto*, auto&&. This explicitness prevents surprises: you always see what qualifiers you're getting.
Syntax
Basic variable deduction
// C++11
auto i = 42; // int
auto d = 3.14; // double
auto s = std::string("hello"); // std::string
auto v = std::vector{1, 2, 3}; // std::vector<int> (C++17 CTAD)Adding qualifiers
const auto x = 42; // const int — prevents modification
auto& r = someVariable; // reference to deduced type
const auto& cr = someValue; // const reference — common for function parameters
auto* p = &someVariable; // pointer to deduced type
auto&& fwd = someExpression; // forwarding reference (C++11)Function return type deduction (C++14)
auto add(int a, int b) { // return type deduced from return statements
return a + b;
}
auto max(double x, double y) -> double { // trailing return type; auto deduction still works
return x > y ? x : y;
}Generic lambda parameters (C++14)
auto lambda = [](auto x) { return x + x; }; // template parameter
lambda(5); // 6 (int)
lambda(2.5); // 5.0 (double)Abbreviated function templates (C++20)
void process(auto container) { // equivalent to template<typename T> void process(T container)
for (auto elem : container) {
// elem deduced for each call
}
}Examples
Range-based for loops
std::vector<std::string> words = {"hello", "world"};
// Read-only — preferred pattern; avoids copy
for (const auto& w : words) {
std::cout << w << '\n';
}
// Modify elements
for (auto& w : words) {
w.push_back('!');
}
// Force a copy (rarely needed)
for (auto w : words) {
std::cout << w << '\n';
}Iterator declarations
std::map<std::string, int> m = {{"a", 1}, {"b", 2}};
// Pre-C++11: verbose
std::map<std::string, int>::iterator it = m.find("a");
// C++11+: concise, readable
auto it = m.find("a"); // type is std::map<std::string, int>::iterator
// Common loop pattern
for (auto it = m.begin(); it != m.end(); ++it) {
std::cout << it->first << ": " << it->second << '\n';
}Containers with exact type deduction
auto size = m.size(); // std::size_t — not int; avoids narrowing issues
auto count = std::count(v.begin(), v.end(), 42); // std::ptrdiff_t
// Correct type prevents subtle bugs with unsigned/signed comparisonsdecltype(auto) — exact type preservation (C++14)
int x = 5;
int& rx = x;
auto a = rx; // int — reference stripped
decltype(auto) b = rx; // int& — reference preserved
// Essential in generic code for perfect forwarding:
template<typename F, typename... Args>
decltype(auto) call(F&& f, Args&&... args) {
return std::forward<F>(f)(std::forward<Args>(args)...);
}Almost Always Auto (AAA) style
// C++11+: explicit braced initialization prevents narrowing
auto x = int{42};
auto name = std::string{"Alice"};
auto v = std::vector<int>{1, 2, 3};
auto fn = [](int a) { return a * 2; };
// vs. old style (easier to narrow unintentionally)
int x = 42;Best Practices
Use auto liberally. It reduces noise, prevents type mismatches, and makes refactoring safer. When you change a function's return type, all callers using auto adapt automatically.
Add qualifiers explicitly when needed. const auto& for read-only references, auto* for pointers, auto& for mutable references. These make intent clear.
Prefer range-based for with const auto& to avoid copies and prevent accidental modification. Use auto& only when you need to modify elements.
Use decltype(auto) in forwarding code to preserve exact types and reference-ness, preventing lifetime issues and unwanted copies in template contexts.
Embrace AAA style in new code. The pattern forces explicit initialization and prevents narrowing bugs. Use braced initialization: auto x = int{42} instead of int x = 42.
Common Pitfalls
Stripping const and references
const std::string& getRef();
auto s1 = getRef(); // std::string — copy made!
const auto& s2 = getRef(); // const std::string& — no copyThis is a design feature, not a bug: auto on its own means "give me the value type." Add qualifiers for the semantics you want.
Proxy types in containers
std::vector<bool> vb = {true, false, true};
auto elem = vb[0]; // std::vector<bool>::reference — not bool
// This proxy behaves like bool but has subtle differences:
elem = false; // modifies vb[0] (usually)
// Correct:
bool b = vb[0];Initializer list deduction
auto a = {1, 2, 3}; // std::initializer_list<int> — may surprise
auto b = int{1}; // int — explicit
auto c = std::vector{1, 2, 3}; // std::vector<int> (C++17+)Recursive function return deduction (C++14)
// ERROR: compiler hasn't seen return type when first recursive call compiles
auto fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2); // fib not yet defined
}
// Correct: trailing return type or explicit specialization
auto fib(int n) -> long long {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}Type Deduction Rules
auto follows template argument deduction (C++11). Qualifiers and reference collapse rules apply:
| Initializer | auto x = ... | auto& x = ... | const auto& x = ... |
|---|---|---|---|
int i | int | int& | const int& |
const int i | int | const int& | const int& |
int& r | int | int& | const int& |
int&& r | int | ill-formed | const int& |
C++17 addition: auto with class template argument deduction (CTAD) eliminates the need to specify template arguments for many standard library types:
auto v = std::vector{1, 2, 3}; // deduces std::vector<int>
auto p = std::pair{1, 2.0}; // deduces std::pair<int, double>See Also
- decltype — Extract the type of an expression without evaluating it
- Structured bindings — Unpack tuples and aggregates with
auto [x, y] = ...(C++17) - Lambda expressions — Generic lambdas with
autoparameters (C++14) - std::invoke — Call any callable with type deduction (C++17)