Automatic Type Deduction using auto

This lesson covers how we can ask the compiler to infer what types we are using through the auto keyword

Ryan McCombe
Updated

When we're initializing a variable with a value, the compiler can automatically infer the type our variable should have. We can ask the compiler to infer this using the auto keyword.

Below, we're initializing MyNumber using an int literal, so the compiler will set MyNumber's type to int:

// This will create an int
auto MyNumber { 5 };

Most editors, including Visual Studio, will let us inspect what the type was deduced to be by hovering over the variable name.

Types can also automatically be deduced in a variety of contexts. For example, when initializing a variable using an expression, such as a function call, its type can also be deduced from the return type of that expression.

Below, GetFloat()'s return type is float, so MyNumber will have a type of float:

float GetFloat() {
  return 3.14;
}

// Will be float
auto MyNumber { GetFloat() };

Dynamic Typing vs auto

Some may have experience with other programming languages that have a dynamic type system. There, the type of a variable can be changed during our program's execution. The following example is valid JavaScript:

var MyVariable = 10;
MyVariable = "Hello World";

It is important to note that auto is not an implementation of dynamic types. auto will fix the variable type as soon as it is created. Then, any future assignment will try to convert the new value to that type.

Below, we create an int variable, and then try to update it by converting the new value to that same type. In this case, the conversion is impossible, so we get a compilation error:

auto MyVariable { 10 };
MyVariable = "Hello World";
error: cannot convert from 'const char [12]' to 'int'

For the same reason, we also can't do this:

auto MyVariable;
error: 'MyVariable': a symbol whose type contains 'auto' must have an initializer

If we try to use auto without providing an initial value, the compiler cannot infer what to set its type to.

C++ does support dynamic typing, and we cover it in the advanced course. However, C++ has a strong type system, and it's generally designed such that code written in C++ will be using that type system heavily.

Function Return Types Using auto

The auto keyword can also be used to automatically determine the return type of a function. This can be inferred by how the function uses the return statement.

Below, GetDouble() will have its return type correctly deduced to double., as we're returning a double literal.

Because of this, MyNumber will also have its return type inferred as a double:

auto GetDouble() {
  return 3.14;
}

auto MyNumber { GetDouble() };

This depends on all of our return statements deducing to the same type. The following will not work, as the compiler cannot determine whether the return type should be float or double:

auto GetDouble() {
  if (SomeCondition) {
    return 9.8f;
  }
  return 3.14;
}
error: 'double': all return expressions must deduce to the same type: previously it was 'float'

If we convert all return types to a consistent type, our code will compile again. In the following example, the return type of GetDouble will correctly be inferred as double:

auto GetDouble() {
  if (SomeCondition) {
    return static_cast<double>(9.8f);
  }
  return 3.14;
}

Were we to specify the return type, the compiler would implicitly convert the returned values to that type, as we've seen before.

Below, our code will compile, with our function returning a double with the value of 9.8 if SomeCondition is true:

double GetDouble() {
  if (SomeCondition) {
    return 9.8f;
  }
  return 3.14;
}

Function Paramaters With auto (C++20)

As of C++20, auto can also be used with function parameters:

auto Add(auto x, auto y) {
  return x + y;
}

auto NumberA { Add(1, 2) };
auto NumberB { Add(1, 2.0) };

We're calling the Add() function with two integers, so x and y in that function will have the int type.

Then, we return the result of summing two int objects, therefore the return type of Add() will also be deduced to be an int.

Finally, because NumberA is being initialized with an expression that returns an int, its type will also be an int.

The same process applies to NumberB, which will have a type of double.

Qualifiers: auto Pointers, auto References

As with any type, we can add qualifiers to our auto declarations. For example, we can create references and pointers to automatically deduced types:

int x { 5 };

// Reference to an automatically deduced type
auto& ReferenceToX { x };

// Pointer to an automatically deduced type
auto* PointerToX { &x };

Should We Use auto?

Generally, we should only use auto if it makes our code clearer. The main scenario where this is the case is where it reduces unnecessary noise.

Typically this involves scenarios where we're repeating a complex type multiple times within the same expression:

SomeNamespace::SomeType MyObject {
  static_cast<SomeNamespace::SomeType>(Target)
};

Adding auto here will reduce the noise, and not hide the type as we're already specifying it in the static_cast parameter anyway:

auto MyObject {
  static_cast<SomeNamespace::SomeType>(Target)
};

Whilst it can save a few keystrokes, using auto can make our code more difficult to read, especially as it gets more complicated.

Generally, auto should not be used if our only reason is to avoid some keystrokes. In the future, we'll see more and more complex types. If our type is verbose and repeated often in our file, we can be tempted to use auto.

But we should consider a type alias instead. After adding the following using statement, we can refer to our type simply as Party, rather than being tempted to use auto:

using Party =
  std::vector<std::pair<Character*, bool>>;

Party MyParty{ GetParty() };

There are two main problems with auto:

Determining the Type Requires Tool Assistance

We've seen how in most IDEs, we can easily hover over a type to determine its type, even if the code is using auto. However, hovering is still slower than reading, and code isn't always opened in an IDE.

In larger projects, code is often read in tools that don't provide the functionality of IDEs. A common example is source control applications such as GitHub.

Determining the Type Requires our Code be Compilable

Secondly, even if our editors can show us how auto is being interpreted, that capability often requires our code to be in a compilable state.

If we introduce an issue that prevents code from being compilable, we often lose visibility on the types we were relying on being deduced by the compiler. This can make the issue even more difficult to debug.

In the remainder of this course and future courses, we adopt a fairly conservative use of auto. We avoid it by default but will use it if we think it makes the code clearer.

This generally falls in line with Google's style guide:

Use type deduction only to make the code clearer or safer, and do not use it merely to avoid the inconvenience of writing an explicit type.

When judging whether the code is clearer, keep in mind that your readers are not necessarily on your team, or familiar with your project, so types that you experience as unnecessary clutter will very often provide useful information to others

However, many others recommend avoiding auto as much as possible:

Always be explicit about the type you're initializing. This means that the type must be plainly visible to the reader.

...

It's very important that types are clearly visible to someone who is reading the code. Even though some IDEs are able to infer the type, doing so relies on the code being in a compilable state. It also won't assist users of merge/diff tools, or when viewing individual source files in isolation, such as on GitHub.

Summary

This lesson introduced the concept of automatic type deduction, explaining how the auto keyword enables the compiler to infer variable types. The key topics points included:

  • The auto keyword allows the compiler to deduce the type of a variable from its initializer.
  • auto is distinct from dynamic typing found in languages like JavaScript; once a type is deduced, it cannot be changed.
  • The lesson explains how auto can be used for function return types and function parameters.
  • It discusses the use of auto in creating function templates, where the compiler generates functions based on the template's usage.
  • We should use auto sparingly and only to improve clarity and readability, not simply convenience.
Next Lesson
Lesson 49 of 60

Constants and const-Correctness

Learn the intricacies of using const and how to apply it in different contexts

Have a question about this lesson?
Answers are generated by AI models and may not have been reviewed for accuracy