Skip to content

← Values, Types, Initialisation step 4 of 4

Medium Primitives

auto copies, and does not say so

for (auto item : items) {
    item.count += 1;
}

This compiles, runs, and does nothing. Not “does something subtly wrong” — nothing at all. The loop body increments a field on a copy that is destroyed at the end of each iteration, and items is untouched when it finishes.

What auto actually does

auto deduces the type of the initialiser and then strips references and top-level const. So:

const std::string& ref = get_name();

auto        a = ref;   // std::string        — a copy, and mutable
auto&       b = ref;   // const std::string& — bound, cannot mutate
const auto& c = ref;   // const std::string& — bound, says so
auto&&      d = ref;   // const std::string& — binds to anything

Only a copies. The other three are the same object under a different name.

This is not a quirk; it is the same rule template argument deduction uses, and auto was defined to match it deliberately. But the consequence is that auto alone means “a fresh independent value”, which is very often not what was intended.

Two failure modes, one cause

  1. Mutation goes nowhere. The loop above. Silent, and the test that catches it is the one that checks the container afterwards.

  2. A copy you did not want. For a std::string or a std::vector that is an allocation per iteration. performance-for-range-copy exists because this is common enough to have a lint of its own.

The four forms, and when each is right

Form Use when
const auto& Reading. The default for a range-for.
auto& Mutating the element in place.
auto You genuinely want an independent copy, or the type is cheap — int, a pointer, an iterator.
auto&& Generic code, or iterating something that yields temporaries (std::vector<bool>, views, zip).

If you take one habit from this problem: write const auto& in a range-for by default, and change it only when you mean to.

Your task

struct Word {
    std::string text;
    int count;
};

void tally(std::vector<Word>& words, const std::string& target);

Increment count on every Word whose text equals target, in place.

The starter is written the way it usually gets written first. It compiles cleanly, and every test fails.