Skip to Content
Course content

9: Type Inference with auto

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this happen to dozens of junior devs, and honestly, I've done it myself. You're trying to update some data in a collection, the code compiles perfectly, but when you run it, the data just... doesn't change. You stare at the code for ten minutes, convinced the logic is sound, only to realize you've fallen into the auto copy trap.

#include <iostream>
#include <vector>
#include <string>

struct Item {
    std::string name;
    double price;
};

int main() {
    std::vector<Item> inventory = { {"Sword", 10.0}, {"Shield", 20.0} };

    // We want to give everything a 10% discount
    for (auto item : inventory) {
        item.price *= 0.9; 
    }

    std::cout << "Sword price: " << inventory[0].price << std::endl; 
    // Expected: 9.0 | Actual: 10.0
    return 0;
}

The Mystery of the Unchanged Vector

If you look at the loop above, it looks perfectly natural. We're iterating through the inventory, and for every item, we reduce the price. But if you run this, the price remains 10.0. Why?

The culprit is how auto handles type deduction. By default, auto behaves like a value type. When the compiler sees for (auto item : inventory), it deduces that item should be of type Item. It isn't creating a reference to the object inside the vector; it's creating a brand new copy of that object for every iteration of the loop.

You're successfully discounting the price of a temporary object that exists for a fraction of a second and then vanishes. The original object sitting inside the vector is left completely untouched.

Fixing the Deduction with References

To fix this, we have to be explicit about our intent. If we want to modify the original data, we need auto to deduce a reference. We do that by adding the ampersand:

for (auto& item : inventory) {
    item.price *= 0.9; 
}

Now, item is deduced as Item&. No copies are made, and the changes persist in the vector. I usually tell people to make this their default habit: if you're iterating over a collection of objects (anything larger than a simple int or char), use auto& to avoid the performance hit of copying, or const auto& if you only need to read the data.

Static Typing in Disguise

One thing that trips people up is the assumption that auto makes C++ act like Python or JavaScript. It doesn't. C++ is still a statically typed language. The compiler isn't "waiting to see what happens" at runtime; it is calculating the exact type at compile time based on the initializer.

This is incredibly useful when dealing with complex types. Remember those nightmare iterator declarations from earlier versions of C++? Instead of writing this:

std::map<std::string, std::vector<int>>::iterator it = myMap.begin();

You can just write:

auto it = myMap.begin();

The compiler knows exactly what begin() returns. You get the brevity of a dynamic language without sacrificing the type safety and speed of C++.

When to Put the auto Away

Just because you can use auto everywhere doesn't mean you should. I've walked into code reviews where every single variable was auto, and it was a nightmare to read. If you're assigning a value from a function call where the return type isn't obvious, auto hides critical information from the next person reading your code (which might be you in six months).

Use auto when the type is redundant (like with iterators or constructors) or when the type is so complex it would hinder readability. If the type is a simple int or bool and it makes the code clearer to state it explicitly, just state it.




📋 Practical Task

Refactoring the Inventory Map Iterator

You have been handed a legacy piece of code that manages a warehouse. The current code uses verbose, old-school iterator declarations that are hard to read. Your task is to refactor the updateStock function to use auto.

Requirements:

  • Replace the complex map iterator declaration with auto.
  • Ensure that the stock quantities are actually updated in the map (think carefully about whether you need auto, auto&, or const auto& for the loop variable).
  • The code must compile and successfully reduce the stock of "Wrench" to 45.
#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> warehouse = {
        {"Hammer", 100},
        {"Wrench", 50},
        {"Screwdriver", 75}
    };

    // TODO: Refactor this loop to use 'auto' 
    // The goal is to subtract 5 from every item's stock
    for (std::map<std::string, int>::iterator it = warehouse.begin(); it != warehouse.end(); ++it) {
        it->second -= 5;
    }

    // TODO: Refactor this range-based loop to use 'auto'
    // We specifically want to check if "Wrench" was updated to 45
    for (const std::pair<std::string, int>& entry : warehouse) {
        if (entry.first == "Wrench") {
            std::cout << "Wrench stock: " << entry.second << std::endl;
        }
    }

    return 0;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.