Improving Stability with Modern C++, Part 3 — The auto Keyword

Ralph Kootker
FactSet
Published in
3 min readNov 17, 2021

--

This week’s installment of the modern C++ blog covers an obsolete keyword from C, auto. It used to mean that a variable had automatic storage duration, the opposite of static. But any variable that isn't static is auto by default, so it was effectively useless. Beginning with C++11, auto triggers type deduction similar to how templates work. It enables code to be more concise, can improve readability, and in some cases even improve performance.

Overview

The primary motivation for auto involves types that are awkward or impossible to spell. The compiler already has enough information to know the type at initialization. It's not necessary for the programmer to repeat it. Each of the following pairs of lines are equivalent.

// Does anyone ever use this type correctly?
std::string::size_type length = str.size();
auto length = str.size();

// The important bit is that 'iter' is an iterator, not what kind
// it is.
std::vector<int>::const_iterator iter = collection.cbegin();
auto iter = collection.cbegin();

// The right side of this expression says I want a shared_ptr to Foo
// why repeat myself?
std::shared_ptr<Foo> bar = std::make_shared<Foo>();
auto bar = std::make_shared<Foo>();

// This is a lambda expression, new in C++11. It has no type
// available to the programmer and can only be declared using auto.
// We'll cover lambdas in a future blog post.
const std::string greeting = "Hello, ";
auto lambda = [&greeting](const Person& employee) -> std::string {
return greeting + employee.name;
};

The first example above bears repeating. As mentioned last time, it’s tempting to store the size of a container as int. For small sizes you can get away with it. But it has caused us portability issues for large containers because the type of size() is different for 32 and 64-bit systems. It's an unsigned type too. With auto we always know we have the correct type.

How can auto help with performance?

Getting the verbose types right isn’t just for readability and correctness. Consider the following loops. They are equivalent except for one seemingly minor issue.

std::map<int, LargeObject> mapping = { /* ... */ };for (const std::pair<int, LargeObject>& kv : mapping) {
// ...
}
for (const auto& kv : mapping) {
// ...
}

Can you spot the problem with the first loop? The loop variable is declared as a reference-to-const, that’s good. And we even used a range-based for-loop from last time. What’s the deal?

The issue is the key type of a std::map<K, V> element is const K. Leaving off the const creates a temporary copy of each element, with the reference extending its lifetime for the duration of the loop body. It compiles and probably runs correctly, but doesn't do what the programmer expected. If the elements are large, the extra copies could incur a performance penalty. It's a subtle bug, one that auto cleanly avoids.

A word about pointers and references

Consistent with the template type deduction rules, auto will deduce pointer types but not references.

std::shared_ptr<Foo> foo = { /* ... */ };
auto bar = foo.get(); // type is T*
auto baz = *foo; // type is T, creates a COPY
std::vector<LargeObject> collection = { /* ... */ };
for (const auto elem : collection) {
// Each 'elem' is a COPY
}

To avoid confusion, we recommend always being explicit about pointer and reference types.

std::shared_ptr<Foo> foo = { /* ... */ };
auto* bar = foo.get();
auto& baz = *foo;
std::vector<LargeObject> collection = { /* ... */ };
for (const auto& elem : collection) {
// ...
}

Should I use auto everywhere?

Probably not. The C++ Core Guidelines recommend auto whenever the type name would be redundant. Herb Sutter (chair of the C++ standards committee) teaches an almost always auto style, which in our opinion goes too far. Someone reading your code needs to know what concept each object represents and what operations are legal. If that's clear enough from the name and how it's initialized, then auto is fine. If not, or if you have a simple type like int or bool, then write the proper type.

What’s next?

Next time we’ll cover improvements to memory management in C++11. You’ll never have to write new or delete again!

Articles in this series

Acknowledgments

Special thanks to all that contributed to this blog post:

Author: Michael Kristofik
Managing Editor: Ralph Kootker
Reviewers: Jim Arena, Jennifer Ma, Jens Maurer, Tim Severeijns, and Manuel Sierich

--

--

Ralph Kootker
FactSet
Writer for

I publish on behalf of others or myself. Please carefully look at the acknowledgements at the bottom of each article