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
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
3D art showing a fantasy maid character
Ryan McCombe
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.

Screenshot showing Visual Studio deducing an type declared as auto

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() };

auto vs Dynamic Typing

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. 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. The equivalent code in C++ 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 tell what to set its type to.

C++ does support dynamic typing, and we cover it in the next course. However, it is a niche concept that is rarely used, because C++ is designed as a strongly typed language.

auto with Function Return Types

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 int:

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

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() {
  return 3.14;
  if (SomeCondition) {
    return static_cast<double>(9.8f);
  }
}

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() {
  return 3.14;
  if (SomeCondition) {
    return 9.8f;
  }
}

auto with Function Parameters (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.

Function Templates

There is some additional nuance when our functions have auto parameters. Above, we’re not defining a function at all - we’re defining a function template.

The compiler can use function templates to automatically create functions at compile time, based on how we use that template through the rest of our code.

In this case, we’re using the template with two unique argument lists - (int, int) and (int, double). As such, the compiler will generate two functions - one with the prototype int Add(int, int) and a second with the prototype double Add(int, double)

Templates are an advanced topic - we explore them more deeply in the next course.

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:

1. Determining the type of auto variables 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.

2. Determining the type of auto variables 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.

Preview of the Next Lesson:

In the next lesson, we will delve into the world of constants and const-correctness in C++. This lesson will explore the importance of immutability in programming, teaching you how to effectively use const to enhance the safety and clarity of your code.

We will also discuss best practices for const-correctness in various programming scenarios. Key topics covered in this lesson will include:

  • Understanding the concept of constants and their role in programming.
  • The use of the const keyword with variables, functions, and pointers.
  • Techniques for implementing const-correctness in code to prevent unintended modifications.
  • Best practices and common pitfalls in using const in programming.

Was this lesson useful?

Next Lesson

Constants and const-Correctness

Learn the intricacies of using const and how to apply it in different contexts
3D art showing a fantasy maid character
Ryan McCombe
Ryan McCombe
Updated
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
Clean Code
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access

This course includes:

  • 56 Lessons
  • Over 200 Quiz Questions
  • 95% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Constants and const-Correctness

Learn the intricacies of using const and how to apply it in different contexts
3D art showing a fantasy maid character
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved