Skip to content

C++ auto type deduction

auto asks the compiler to deduce a type from an initializer. The variable still has one fixed compile-time type; C++ does not become dynamically typed.

Brief example

#include <iostream>
#include <string>

int main()
{
    auto count = 3;                 // int
    auto temperature = 21.5;        // double
    auto name = std::string{"imu"}; // std::string

    std::cout << name << ": " << count << " samples at "
              << temperature << " C\n";
}

Every declaration needs an initializer so the compiler has a type to deduce:

auto count = 3; // Valid: int
auto missing;   // Error: no initializer

Why use auto?

Use auto when the initializer already makes the type clear or when spelling the type adds noise:

const auto iterator = readings.find("temperature");
const auto result = calculate_position();

An explicit type is often clearer when the type communicates an important unit, range, or conversion:

std::uint32_t timeout_ms = 1000;
double distance_m = sensor.read_distance();

auto is a readability tool, not a rule that every type must be hidden.

Value, reference, and const deduction

Plain auto creates a new value. It drops top-level const and does not keep a reference from the initializer.

1
2
3
const int original = 5;
auto copy = original; // int
copy = 9;             // Does not change original

Add qualifiers according to the behavior you need:

Declaration Meaning
auto value = expression; Make a copy or move a new value.
auto& value = expression; Create a modifiable reference.
const auto& value = expression; Read without copying; can bind to a temporary.
auto&& value = expression; Preserve the expression's value category during deduction.
1
2
3
4
5
6
7
8
std::string label{"camera"};

auto copy = label;
auto& reference = label;
const auto& read_only = label;

reference = "lidar"; // Changes label
copy = "gps";        // Does not change label

Start with intent

Use auto for an independent value, auto& to modify the original, and const auto& to read a potentially expensive value without copying it.

Loops

const auto& is a useful default for reading container elements:

for (const auto& reading : readings)
    std::cout << reading << '\n';

Use auto& when the loop must modify each element:

for (auto& reading : readings)
    reading *= 2.0;

Use plain auto only when you intentionally want a copy of every element.

Function return types

Since C++14, a function can deduce its return type from its return statements:

1
2
3
4
auto square(int value)
{
    return value * value;
}

All return paths must deduce the same type. The function definition also normally needs to be visible before code calls it because the compiler must see the body to know the return type.

Use an explicit return type when it documents the interface better or when implicit conversions between return expressions are intended.

Generic lambdas

C++14 also allows auto in lambda parameters:

1
2
3
4
5
6
const auto add = [](const auto& left, const auto& right) {
    return left + right;
};

const auto integer_sum = add(2, 3);
const auto text = add(std::string{"front"}, std::string{" camera"});

The compiler creates a suitable call operator for each compatible argument-type combination.

auto and structured bindings

Structured bindings use auto to deduce the decomposed element types:

const std::pair<int, double> reading{4, 22.5};
const auto [sensor_id, temperature] = reading;

Use auto& or const auto& when the bindings should refer to the original object. See C++ structured bindings for the complete lesson.

Braced initialization

These similar-looking declarations deduce different types:

auto first{1};    // int
auto second = {1}; // std::initializer_list<int>

Mixed element types cannot produce one initializer-list type:

auto values = {1, 2.5}; // Error: int and double do not match

Prefer direct initialization such as auto value = Type{...} when the intended type should be obvious.

decltype(auto)

decltype(auto) follows decltype rules and can preserve references that plain auto would discard:

1
2
3
4
decltype(auto) first(std::vector<int>& values)
{
    return (values.front()); // Returns int& because the expression is parenthesized
}

This is useful in forwarding code, but it is easy to return a dangling reference. Prefer an explicit return type unless preserving the exact expression type is necessary.

Common mistakes

  • Declaring auto without an initializer.
  • Assuming plain auto keeps a reference or top-level const.
  • Copying large elements in a loop when const auto& was intended.
  • Hiding a meaningful conversion or unit behind auto.
  • Expecting one auto variable to change type after declaration.
  • Accidentally deducing std::initializer_list with auto value = {...}.

Quiz

1. Does copy modify original?

1
2
3
4
const int original = 4;
auto copy = original;
copy = 9;
std::cout << original;
Choose one answer:

2. Which declaration reads a large element without copying it?

Choose one answer:

3. What type does values have?

auto values = {1, 2, 3};
Choose one answer:

Further reading