Skip to content
C++
Language
since C++11
Beginner

auto

Type deduction keyword that deduces variable types from initializers, function returns, and parameters. Strips top-level const/references unless explicitly marked.

autosince C++11

Placeholder 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

cpp
// 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

cpp
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)

cpp
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)

cpp
auto lambda = [](auto x) { return x + x; };  // template parameter
lambda(5);              // 6 (int)
lambda(2.5);            // 5.0 (double)

Abbreviated function templates (C++20)

cpp
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

cpp
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

cpp
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

cpp
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 comparisons

decltype(auto) — exact type preservation (C++14)

cpp
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

cpp
// 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

cpp
const std::string& getRef();

auto s1 = getRef();      // std::string — copy made!
const auto& s2 = getRef();  // const std::string& — no copy

This 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

cpp
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

cpp
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)

cpp
// 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:

Initializerauto x = ...auto& x = ...const auto& x = ...
int iintint&const int&
const int iintconst int&const int&
int& rintint&const int&
int&& rintill-formedconst int&

C++17 addition: auto with class template argument deduction (CTAD) eliminates the need to specify template arguments for many standard library types:

cpp
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 auto parameters (C++14)
  • std::invoke — Call any callable with type deduction (C++17)