auto
auto
keywordWhen 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() };
auto
vs Dynamic TypingSome 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 TypesThe 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 return
ing 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
.
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.
auto
Pointers, auto
ReferencesAs 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 };
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
:
auto
variables requires tool assistanceWe’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.
auto
variables requires our code be compilableSecondly, 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.
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:
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.auto
can be used for function return types and function parameters.auto
in creating function templates, where the compiler generates functions based on the template's usage.auto
sparingly and only to improve clarity and readability, not simply convenience.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:
const
keyword with variables, functions, and pointers.const
in programming.auto
This lesson covers how we can ask the compiler to infer what types we are using through the auto
keyword
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way