Programming in C++

auto

Gerald Senarclens de Grancy

The auto keyword

Using auto, the compiler deducts variable types based on their initial value

The rules for type inference are essentially the same used for templates

  • Types are still fixed at compile time
  • Once assigned, the type cannot change
  • No performance overhead

Advantages of auto

Using auto makes your code more resilient and readable.

  • auto variables have to be initialized
  • Avoid narrowing conversions and compiler warnings
  • Facilitate refactoring
  • Convenient - particularly for Voldemort types

Disadvantages of auto

  • Can obscure code by "hiding" types from programmers

Checking Types

To better understand what auto does, check the deducted types

typeid(.).name() denotes the implementation defined name of the type

ABI for C++ programs contains information on builtin types

Example

#include <iostream>
#include <typeinfo>

int main() {
  int x{42};
  std::cout << "Type of x: " << typeid(x).name() << "\n";  // output: i

  const int ci{42};
  std::cout << "Type of ci: " << typeid(ci).name() << "\n";  // output: i

  unsigned long int uli{42};
  std::cout << "Type of uli: " << typeid(uli).name() << "\n";  // output: m

  double d{3.14};
  std::cout << "Type of d: " << typeid(d).name() << "\n";  // output: d
  return 0;
}

Visualize execution

Defining Variables Using auto

Example: Literals

#include <iostream>
#include <typeinfo>
#include <vector>
using std::string_literals::operator""s;  // required for the `s` suffix
template <typename T>
void print_type(const char* name, T arg) {
  std::cout << "Type of " << name << ": " << typeid(arg).name() << std::endl;
}

int main() {
  auto i{42};
  print_type("i", i);  // i(nt)
  const auto ci{42};
  print_type("ci", ci);  // i(nt)
  print_type("ui", 42u);  // j
  print_type("l", 42l);  // l
  auto ul{42ul};
  print_type("ul", ul);  // m
  auto ull{42ull};
  print_type("ull", ull);  // y
  auto f{3.14f};
  print_type("f", f);  // f
  print_type("d", 3.14);  // d
  auto s{"foo"};
  print_type("s", s);  // PKc
  auto str = "foo"s;
  print_type("str", str);  // Ss
  return 0;
}

Visualize execution

Example: Containers

#include <iostream>
#include <typeinfo>
#include <vector>

template <typename T>
void print_type(const char* name, T arg) {
  std::cout << "Type of " << name << ": " << typeid(arg).name() << std::endl;
}

int main() {
  auto v{std::vector<int>{1, 2, 3}};
  print_type("v", v);  // [...]vector[...]
  auto p{std::pair<int, char>{2, 1}};
  print_type("p", p);  // [...]pair[...]
  return 0;
}

Visualize execution

Example: Voldemort Types

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

int main() {
  auto v{std::vector<std::string>(3, "foo")};
  for (std::vector<std::string>::const_iterator it{v.cbegin()}; it != v.cend(); ++it) {
    std::cout << *it << ", ";
  }
  for (auto it{v.cbegin()}; it != v.cend(); ++it) {
    std::cout << *it << ", ";
  }
  std::cout << std::endl;
  return 0;
}

Visualize execution

Example: Anonymous Functions Without Auto

#include <functional>
#include <iostream>
#include <vector>

std::function<std::vector<int>::size_type(const std::vector<int>&,
                                          const std::vector<int>&)>
  sum_sizes = [](const std::vector<int>& v1, const std::vector<int>& v2)
    { return v1.size() + v2.size(); };

int main() {
  auto v1{std::vector<int>{1, 2, 3}};
  auto v2{std::vector<int>(4, 5)};
  std::cout << "total size of v1 and v2: " << sum_sizes(v1, v2) << "\n";
  return 0;
}
Download lambda.cpp

Example: Anonymous Functions

#include <iostream>
#include <vector>

auto sum_sizes = [](const std::vector<int>& v1, const std::vector<int>& v2)
  { return v1.size() + v2.size(); };

int main() {
  auto v1{std::vector<int>{1, 2, 3}};
  auto v2{std::vector<int>(4, 5)};
  std::cout << "total size of v1 and v2: " << sum_sizes(v1, v2) << "\n";
  return 0;
}
Download lambda_auto.cpp

Example: Anonymous Functions Auto Parameters (C++14)

#include <iostream>
#include <vector>

auto sum_sizes = [](const auto& v1, const auto& v2)
  { return v1.size() + v2.size(); };

int main() {
  auto v1{std::vector<int>{1, 2, 3}};
  auto v2{std::vector<int>(4, 5)};
  std::cout << "total size of v1 and v2: " << sum_sizes(v1, v2) << "\n";
  return 0;
}
Download lambda_all_auto.cpp

Explicitly Typed Initializer Idiom

Sometimes, auto may deduce undesired types

Scott Meyers' explicitly typed initializer idiom can be used instead of falling back to explicit typing

Type value{get_value()};  // explicit typing
auto value{static_cast<Type>(get_value())};  // explicitly typed initializer

Example

Embedded system must store the result of external code as float

#include <iostream>

double get_value () { return 0.5; };  // dummy for external calculation

int main() {
  auto d{get_value()};  // not the desired type
  std::cout << "Type of d: " << typeid(d).name() << "\n";
  // float f0{get_value()};  // compiler error
  // std::cout << f0 << "\n";
  float f1 = get_value();  // possible compiler warning (-Wconversion)
  float f2{static_cast<float>(get_value())};  // duplication of type
  auto val{static_cast<float>(get_value())};
  std::cout << "val: " << val << " f1: " << f1 << " f2: " << f2 << "\n";
  return 0;
}
Download etii.cpp

Summary

Almost always auto (AAA) unless you have a reason not to

  • auto variables must be initialized
  • explicitly typed initializer idiom forces auto to deduce desired type

Questions
and feedback...

Further Reading

Herb Sutter GotW #94 Solution: AAA Style (Almost Always Auto) https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/
Scott Meyers Effective Modern C++, pg. 37-48 O'Reilly and Associates (2014-12-05) buy on Amazon