string_view
operator+
vs. StrCat()
absl::Status
std::bind
absl::optional
and std::unique_ptr
absl::StrFormat()
make_unique
and private
Constructors.bool
explicit
= delete
)switch
Statements Responsibly= delete
AbslHashValue
and Youcontains()
std::optional
parametersif
and switch
statements with initializersinline
Variablesstd::unique_ptr
Must Be MovedAbslStringify()
vector.at()
auto
for Variable Declarationsauto
for Variable DeclarationsOriginally posted as TotW #232 on June 20, 2024
By Kenji Inoue and Michael Diamond, Google Engineer
Updated 2024-09-30
Quicklink: abseil.io/tips/232
The style guide says in the Type Deduction (including auto) section:
Use type deduction only if it makes the code clearer to readers who aren’t familiar with the project, or if it makes the code safer. Do not use it merely to avoid the inconvenience of writing an explicit type.
Ironically, overuse of auto
often leads to code becoming less clear. Over
time, however, several patterns have emerged where using auto
can improve code
clarity and safety, such as:
for
loops over a map.We’ll discuss each of those cases below, with an eye toward clarifying the cases
in which auto
makes code safer or clearer.
The following code has a problem that each element in the map is unintentionally copied:
absl::flat_hash_map<std::string, DogBreed> dog_breeds_by_name = ...; // `name_and_breed` is copy-constructed for each element of the map. for (const std::pair<std::string, DogBreed>& name_and_breed : dog_breeds_by_name) { ... }
The unintended copy happens because the value_type
of associative containers
is std::pair<const Key, Value>
and std::pair
allows implicit conversions
between pair objects if their underlying types can be implicitly converted.
Because std::pair::first_type
here is std::string
and the map entry here has
std::pair::first_type
of const std::string
, the pairs are not the same type
and an implicit conversion occurs, copying the contents of the pair despite
name_and_breed
being declared as a reference.
Using auto
, possibly in conjunction with structured bindings
(Tip #169), can make the code safer and more performant:
absl::flat_hash_map<std::string, DogBreed> dog_breeds_by_name = ...; // `auto` with structured bindings - if the element types are clear from local // context. for (const auto& [name, breed] : dog_breeds_by_name) { ... }
Sometimes, the element types are not obvious from local context. In that case, you can do this:
// `auto` without structured bindings - allows specifying the element types. for (const auto& name_and_breed : dog_breeds_by_name) { const std::string& name = name_and_breed.first; const DogBreed& breed = name_and_breed.second; ... }
The names of iterator types are verbose and often provide redundant type information when the type of the container is visible nearby.
Here is an example code snippet that assigns an iterator to a local variable.
std::vector<std::string> names = ...; std::vector<std::string>::iterator name_it = names.begin(); while (name_it != names.end()) { ... }
All containers expose begin()
and end()
functions which return iterators,
and these iterators have type ContainerType::iterator
or
ContainerType::const_iterator
.
When the type of the container is visible nearby, calling out these types would
only have a small benefit of differentiating iterator
and const_iterator
because the container type part (e.g., std::vector<std::string>
) is the same
as that of the container. In this case, we can use auto
to remove redundancy
without hiding helpful information:
std::vector<std::string> names = ...; auto name_it = names.begin(); while (name_it != names.end()) { ... }
When the container type is not visible locally, prefer to spell out the full iterator type or element type:
std::vector<std::string>::iterator name_it = names_.begin(); while (name_it != names_.end()) { ... }
auto name_it = names_.begin(); while (name_it != names_.end()) { const std::string& name = *name_it; ... }
std::make_unique
and Other Google-wide Factory FunctionsIn the following code snippet, std::make_unique
and
proto2::MakeArenaSafeUnique
specify the types to be instantiated.
std::unique_ptr<MyFavoriteType> my_type = std::make_unique<MyFavoriteType>(...); proto2::ArenaSafeUniquePtr<MyFavoriteProto> my_proto = proto2::MakeArenaSafeUnique<MyFavoriteProto>(arena);
It is widely known throughout Google that std::make_unique<T>
returns
std::unique_ptr<T>
and proto2::MakeArenaSafeUnique<T>
returns
proto2::ArenaSafeUniquePtr<T>
. In particular, the important part of the
resulting type T
is specified on the right-hand side (RHS) expression, and it
is company-wide knowledge rather than project-specific knowledge. We can use
auto
here to remove redundancy without hiding helpful information:
auto my_type = std::make_unique<MyFavoriteType>(...); auto my_proto = proto2::MakeArenaSafeUnique<MyFavoriteProto>(arena);
In some circumstances when writing generic code, such as templates or GoogleTest
matchers, the type may be impossible or very difficult to specify (e.g., a type
written with template metaprogramming or decltype
). In these cases auto
may
also be appropriate. However, these situations should be rare.
auto
While it can be tempting to use auto
in situations where the type is long and
seems obvious to you, remember that future readers of the code may not be
familiar with your project and the types it uses
(why). For example, consider a common pattern of
nested proto access.
// Of course `breed` has type `const DetailedDomesticCatBreed&`! const auto& breed = cat.pedigree().detailed_breed();
auto
may also hide basic semantics like constness, whether a type is a
pointer, and whether a copy is being made (Tip #44).
// Did the author mean to make a copy here? // It is not obvious to all readers that `breed` is not a reference even though // `detailed_breed()` returns a reference! auto breed = cat.pedigree().detailed_breed();
// Type and semantics are clear. const DetailedDomesticCatBreed& breed = cat.pedigree().detailed_breed();
auto
when manually writing out a more specific type would incur a high
risk of correctness or performance problems.auto
to remove redundancy without hiding helpful information when the
useful type information is visible locally.auto
may be appropriate; these situations should be rare.auto
in other situations: while it may make it easier for
you to write the code or allow you to avoid a line-break, it probably makes
the code harder to understand for someone unfamiliar with your project.