C++ compilers can often automatically infer the type of the variables we create. We can ask the compiler to do this using the auto
keyword:
// This will create an integer
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.
Our Number
variable predictably has a type of int
, which we can confirm by hovering over the variable in our editor:
Types can also automatically be deduced in a variety of contexts. For example, when initialising a variable, its type can also be deduced from the return type of the function.
float GetFloat() {
return 3.14f;
}
// Will be float
auto Number { GetFloat() };
auto
with C++ FunctionsAuto can also be used to automatically determine the return type of the function. This can be inferred by how the function uses the return
statement.
Below, GetDouble
will have its return type correctly deduced to double
. Therefore, NumberC
will also have a type of double
auto GetDouble() {
return 3.14;
}
auto Number { GetDouble() };
As of C++ 20, auto
can also be used with function parameters:
auto Add(auto x, auto y) {
return x + y;
}
auto Number { Add(1, 2) };
We're calling the add
function with two integers, so x
and y
in that function will be integers.
Then, we are returning the result of summing two integers, therefore the return type of Add
will be deduced to be an integer.
And finally, because Number
is being initialised to that return value, its type will also be an integer.
auto
in Parameter ListsThere is some additional nuance when our functions have auto
parameters.
Such a function isn't actually a function at all - it is a function template.
This is a topic that's slightly out of scope for this course - so I'd recommend avoiding using auto
in this way for now until we understand what is going on.
We cover function templates in detail in the next course
auto
vs Dynamic TypingSome may have experience with other programming languages, where we store data inside variables, regardless of the type of those variables.
This is sometimes called "dynamic typing". It is important to note that auto
is not an implementation of dynamic types.
For example, this code will not compile:
auto MyVariable { 10 };
MyVariable = "Hello World"
The type of MyVariable
is inferred to be an int
as soon as line 1 is executed. It cannot later change to a string. Even though it's type is auto
, that type is determined as soon as our variable is initialised.
For that reason, we also can't do this:
auto MyVariable;
If we try to use auto
without initialising the variable, the compiler has no way to tell what to set its type to.
C++ is a strongly typed language. The type of variables needs to be known. auto
doesn't change that, it just asks the compiler to figure that out for us.
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 auto
should not be used if our only reason is to avoid specifying our data types.
Whilst it can save a few keystrokes, using auto
can make our code more difficult to read, especially as it gets more complicated.
To figure out what type an auto
variable is, we have to hover over it, rather than simply being able to read the type directly. Moreover, we don't always read code in an editor.
In larger projects, code is often read in tools that won't provide visibility on what "auto" is being interpreted as.
Secondly, even if our editors can show us what auto
is being interpreted as, that capability often requires our code being 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 means it can be very difficult to work with complex code that makes excessive use of auto
.
However, there are some scenarios where using auto
makes sense. Most notably, it could be used where it is extremely obvious what our type is, even without tool assistance.
For example, a past lesson had this code:
enum class Faction { Human, Elf, Undead };
Faction Faction { Faction::Undead };
Faction
being repeated 3 times on this short line of code is quite messy. It can also be confusing for beginners, as both the name and type of the variable are the same.
The Faction::
syntax makes it clear this variable is going to be initialised from the Faction
enum, so its type is obvious. Therefore, we might consider using auto
in this scenario:
auto Faction { Faction::Human };
For similar reasons, another scenario where auto
might be preferred is when we are storing the value returned from a named cast:
Monster* Monster { dynamic_cast<Monster*>(Target) };
Given we're already specifying the type we're trying to dynamic_cast
to, it is reasonable to replace the duplicated type with auto
:
auto Monster { dynamic_cast<Monster*>(Target) };
In the remainder of this course, and the more advanced 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.
Up next, we'll see a scenario where we need to use auto
. We'll learn how we can use structured bindings to quickly create a large amount of variables from one of our objects.
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way