|« C++: Template Meta-Programming 2.0||Make Your Comments Matter »|
auto has been given a new behavior since the C++11 Standard was ratified. Instantly I could appreciate the value of its new function when I considered things like declaring an iterator for a container. However, I was skeptical of any value that
auto could provide for general purpose use.
Now that I have had a chance to study this new keyword I believe that it is quite a valuable addition to Modern C++. While there are situations that could still cause some grief, they are not difficult to detect, and the solutions to the problem are straight-forward to apply
Here are a few examples for reference that will give context to this discussion.
auto has been repurposed to allow developers to take advantage of the type information known to the compiler. This provides the potential benefit to make code easier to read and write.
I have added a comment that shows the type that each
auto variable will be assigned. This is also the same declaration that would be required if
auto were not used.
I am not the only programmer to have doubts about allowing the system to automatically choose the types of my variables. There are a number of questions asked around the web like this one from StackExchange and other sites.
There were two fundamental sources of my doubt.
Flashbacks of Visual Basic
I spent a brief moment of my career developing with Visual Basic. (Hey! I only did it to pay for tuition while I was in college. sigh).
To declare a new variable you would use the keyword
Dim. If you hadn't defined a variable explicitly with
Dim, then the compiler would give you an error. Variables that were not given an explicit type in Visual Basic used a type called
If you ever have programmed with Microsoft's COM, then you are aware that a
Variant could be coerced into different types at run-time.
BasicallyEssentially allowing this strongly-typed static language to bend the rules when using the
It did not take me long to realize that
auto in Modern C++ is nothing like a
The chaos of using unknown types
This is only a misconceived notion. The type is well known by the compiler after it verifies the correctness of your code and before it generates any of the final object code. The syntax used in the declarations and statements have well-defined rules to determine these types. This is especially true for C++; any version of C++.
There are subtle coercion rules that have always existed to allow C code to be more compatible with the stronger type-checking that is present in C++. These rules mostly deal with the width of an integer or converting an object from one type to a compatible type without cluttering the syntax.
If you still have doubts, inspect the type of assigned to your variable within your IDE or debugger. There is a definitive type that has been deduced and assigned for every
auto variable. This type is fixed and will not change.
Automatic type deduction
Are you familiar with templates and how the types are deduced for parametric variables? If so, then you know almost all there is to know about how the type is determined for
auto variables. If you aren't familiar with all of the rules, it's OK, because the compiler is.
The rules are the same between the two forms of variable declaration with one exception, which is when a curly-braced initializer list is considered in the type deduction. In this case, the
auto declared variable assumes the type to be an
std::initializer_list that was also added to Modern C++. A
template-deduced type in this situation requires you to explicitly specify the
std::initializer_list type. The type
T contained within the
std::initializer_list<T> will then be deduced by the
I also want to remind you about the existence of Type Decay[^], it seems probable to me that you will encounter this at some point using auto. This coercion rule applies to both
auto type deduction.
C++14 has added the possibility for
auto to be used as the return type for a function.
It is important to note, that every return point from the function must result with the same return type, which also allows the possibility for type conversion of the values into the same type. This is more evidence that demonstrates there is nothing magical about
auto. The final code that it generates follows the same rules as if you had explicitly defined the values yourself.
There is a catch
auto type must use the rules for
template type-deduction. This means that it is not possible to use
auto when you would like to return a
This rule also applies to the type deduction for the evaluation of lambda expressions.
To make this expression valid, the same adjustment required for templates is required. The
std::initializer_list<T> needs to be specified, then the type
T can be deduced.
The advantages of
auto are immediately apparent when you consider the amount of clutter that is removed when you use iterators or a moderately complicated
However, there are other advantages that are not immediately obvious.
Less typing (explicitly and with your keyboard)
Yeah, this is the low hanging fruit. I should mention, there is one exception, and that is if the type you need is an
int. I also suppose there exists sadistic programmers that
typedef or alias, one and two character length type names; so that would be another exception.
Along the same theme of less typing is refactoring data-fields. Changing types or function signatures. If you use
auto effectively, you may only need to update the declaration in the header file and the functions definition to complete your refactor.
auto defined variables will be initialized
When a variable is defined with auto, it must be assigned an initial value. Otherwise there would be no way to determine its type:
One thing that I try to teach developers that I mentor, is to not define the variable until you need it. Many developers still seem to forward declare all of their variables at the top of a function. That isn't necessary. Furthermore, objects with constructors may not be cheap, and if you drop out of the function before you use the constructed object, well that's just wasteful.
I think declaring your variables at the point they become necessary using
auto will help advance this style.
This next set of declarations is a common occurrence, where the programmer explicitly selected the type for the variable, and they almost got it right, or maybe not at all. Selecting the incorrect type may be the difference between correct behavior and a security vulnerability. Another consequence that is less severe, depending on who you ask, would be truncation, or data loss.
It seems that I am always correcting warnings such as this
warning: '<' : signed/unsigned mismatch. While learning secure coding practices I adopted the habit of using unsigned types for values that will be used as an index to reference memory. In fact, std::size_t has become my defacto unsigned type. It is guaranteed to be large enough to reference the largest address on the system.
As it turns out, it's not always possible, or even correct for an unsigned type to be used in comparison operations. Then I am left with a cast of some sort.
auto solves this problem by selecting a compatible type with your initialization value. The most frequent place this seems to occur is within
That is correct. Consider all of the coercion and transformation techniques that may be used by the compiler to allow your program to compile successfully and run efficiently. Techniques such as move-semantics, const-correctness, temporary copies, the list goes on.
I have already argued that we don't always succeed in selecting the correct type just to hold the size of a container. Selecting the best type for efficiency is an even more difficult decision. The most probable place an incorrectly selected type could affect performance is within a loop. However, another time to consider replacing your type declaration to
auto is when you are running a profiler trying to optimize your code, and you identify a hot-spot that seems to be making unnecessary copies. Using an
auto declaration may give the compiler the freedom it needs to select a more efficient type and eliminate those copies.
Be aware of proxy classes
Yes, there is another potential gotcha that is important to know exists. Proxy classes are useful in variety of ways. The list below is not exhaustive, and it is likely the proxy class exists for more than one reason found on the list:
- Transparently provide remote access
- Delay expensive calculations until required
- Encapsulate logic for efficiency
- Protect direct access to encapsulated data
- Formulate expression templates
Libraries commonly use proxy classes to simplify the syntax required to interface with the library objects. Expression templates a very common with math-based libraries to retain the natural mathematic syntax that is possible in C++. A
Matrix class for example, or even a multi-dimensional array.
Example: Alchemy Proxies
I use proxy[^] classes in Alchemy to make the data fields of a message appear to the caller to be a fundamental type that is part of a
struct. Each field is contained within its own proxy object, which knows how to efficiently manage the different operations supported in Alchemy.
Each proxy class contains a type-conversion function to extract the actual value from the proxy object. Here is an example definition of a 3-dimensional point.
A comparison of the results between explicit type declaration and
auto type declaration.
ax_val is not assigned the type
double, as would be desired in most circumstances. That is because the compiler actually does get the same type for the
auto declared variable to the explicitly declared
If that were the end, the result would be a compiler error. However, the proxy class implements a type-conversion function,
operator value_type() const, where this is the definition for
typedef double value_type; Now the compiler has the leeway to coerce the proxy object into the declared type,
double, and the statement becomes valid.
Another name for proxy classes that have conversion operators like this, are also referred to as, invisible proxies.
What is the solution?
First, recognize that
auto is doing exactly what it is supposed to do in this case. Then we can admit that it does not actually arrive at the desired type, a
Item 6 from, Scott Meyer's, Effective Modern C++ offers a solution that he calls the explicitly typed initializer idiom. The solution is to explicitly specify the desired type with a
This scenario is most likely to occur when the library you are using allows for a terse and expressive syntax. When you suspect auto is not assigning the type that you expected, take a look at the definition of the class that owns the expression. If the return type is a proxy object you can either explicitly cast to the desired type, or revert to the classic form of type declaration.
auto has been re-purposed in Modern C++. A cursory glance of the change reveals some benefits. However, the benefits run much deeper than the novelty applications that you can imagine. Adopting a consistent use of
auto to declare your variable types will most likely improve your codes correctness, portability, efficiency, and most of all readability. If you have previously considered
auto, and decided that it wasn't for you, take a moment to reconsider.
auto has great potential.