For example, the loop:
std::vector<int> vec;
...
for (auto& c : vec) { ... }
will iterate over vec and copy each element by reference.
Would there ever be a reason to do this?
for (int& c : vec) { ... }
The two code snippets will result in the same code being generated: with auto
, the compiler will figure out that the underlying type is int
, and do exactly the same thing.
However, the option with auto
is more "future-proof": if at some later date you decide that int
should be replaced with, say, uint8_t
to save space, you wouldn't need to go through your code looking for references to the underlying type that may need to be changed, because the compiler will do it for you automatically.
The question here really is if there is any reason why you shouldn't use auto
for anything you can.
Well, let's get one thing out of the way first of all. The fundamental reason why auto
was introduced in the first place was two-fold.
First, it makes declarations of complex-type variables simpler and easier to read and understand. This is especially true when declaring an iterator in a for
loop. Consider the C++03 psudocode:
for (std::vector <Foo>::const_iterator it = myFoos.begin(); it != myFoos.end(); ++it)
This can become much more complex as the complexity of myFoos
's type becomes more complex. Moreover if the type of myFoos
is changed in a subtle way, but in a way that's insignifigant to the loop just written, the complex declaration must be revisited. This is a maintennance problem made simpler in C++11:
for (auto it = myFoos.begin(); it != myFoos.end(); ++it)
Second, there are situations which arise that are impossible to deal with without the facilities provided by auto
and it's sibling, decltype
. This comes up in templates. Consider (source):
template<typename T, typename S>
void foo(T lhs, S rhs) {
auto prod = lhs * rhs;
//...
}
In C++03 the type of prod
cannot always be inferred if the types of lhs
and rhs
are not the same. In C++11 this is possible using auto
. Alas it is also possible using decltype
, but this was also added to C++11 along with auto
.
Many of the C++ elite suggest that you should use auto
anywhere possible. The reason for this was stated by Herb Sutter in a recent conference:
It's shorter. It's more convinient. It's more future-proof, because if you change your function's return type,
auto
just works.
However they also acknowledge the "sharp edges." There are many cases where auto
doesn't do what you want or expect. These sharp edges can cut you when you want a type conversion.
So on the one hand we have a highly respected camp telling us "use auto
everywhere possible, but nowhere else." This doesn't feel right to me however. Most of the benefits that auto
provide are provided at what I'm going to call "first-write time." The time when you are first writing a piece of code. As you're writing a big chink of brand-new code you can go ahead and use auto
virtually everywhere and get exactly the behavior you expect. As you're writing the code you know exactly what's going on with your types and variables. I don't know about you, but as I write code there is a constant stream of thoughts going through my head. How do I want to create this loop? What kind of thing do I want that function to return so I can use it here? How can I write this so that is fast, correct, and easy to understand 6 months from now? Most of this is never expressed directly in the code that I write, except that the code that I write is a direct result of these thoughts.
At that time, using auto
would make writing this code simpler and easier. I don't have to burden my mind with all the little minute of signed versus unsigned, reference vs value, 32 bit vs 64 bit, etc. I just write auto
and everything works.
But here's my problem with auto
. 6 months later when revisiting this code to add some major new functionality, that buzz of thought that was going through my mind as I first write the code has long been extinguished. My buffers were flushed long ago, and none of those thought are with me anymore. If I have done my job well, then the essence of those thoughts are expressed directly in the code I wrote. I can reverse-engineer what I was thinking by just looking at the structure of my functions and data types.
If auto
is sprinkled everywhere, a big part of that cognizance is lost. I don't know what I was thinking would happen with this datatype because now it's inferred. If there was a subtle relationship taking place with an operator, that relationship is no longer expressed by the datatypes -- it's all just auto
.
Maintenance becomes more difficult because no I have to re-create much of that thought. Subtle relationships become more clouded, and everything is just harder to understand.
So I'm not a fan of using auto
everywhere possible. I think that makes maintenance harder than it has to be. That's not to say I believe that auto
should only be used where it's required. Rather, it's a balancing act. The four criteria I use to judge the quality of my (or anyone's code) are: efficiency, correctness, robustness, and maintainability. In no particular order. If we design a spectrum of auto
use where one side is "purely optional" and the other side is "strictly required", I feel that in general the closer to "purely optional" we get, the more maintainability suffers.
All this to say, finally, that my philosophy can be nutshelled as:
Use
auto
wherever it makes the code better, but nowhere else. Understand the impact usingauto
overly-liberally has on maintainability.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With