Another Opinion on “Almost Always Auto”
by Malte Skarupke
Herb Sutter has been promoting his almost always auto style again, and I think it is harmful. I would agree with “almost always auto” in Scala. I disagree with it in C++. And that’s because there is a slight difference in syntax for type inference between the two languages.
Here’s type deduction in Scala:
val inferred = 0 val typed : Int = 1
And here it is in C++
auto inferred = 0; int typed = 1;
Seems similar, right? But the difference in syntax leads to different long term programmer behavior.
Here’s how explicit typing is explained in the book Programming Scala: A Comprehensive Step-by-Step Guide, 2nd Edition (page 70):
When the Scala interpreter (or compiler) can infer types, it is often best to let it do so rather than fill the code with unnecessary, explicit type annotations. You can, however, specify a type explicitly if you wish, and sometimes you probably should. An explicit type annotation can both ensure the Scala compiler infers the type you intend, as well as serve as useful documentation for future readers of the code.
In Scala adding type information to a variable is purely an additive operation. When you are adding type information, it is seen as improving the code. Another common recommendation is that when you get compiler errors, it often helps to add type annotations to find where the type went wrong.
The important part here is that if you add type information to my code in Scala, I will not remove it again. I probably won’t initially add type information to all my code because often you don’t need it and often it’s faster to just leave it out. (all of Herb Sutter’s arguments also apply)
But Scala’s design ensures that in the long term your code will gather more and more type annotations. Where if you follow Herb Sutter’s advice in C++, that is not the case.
So if I had to translate the Scala method to C++ it would be “start off with auto, but whenever you get the chance, make the types explicit.” But I don’t like this rule. It makes sense in Scala because in Scala type inference is the default. In C++ there is no default, so why recommend doing the version that will benefit from being changed later?
As for Herb’s specific arguments: He is convinced that using auto does not decrease readability. He uses a template as an example of where we already don’t have type information. Which is ironic, because templates are notoriously unreadable. There are reasons why so many people are intimidated by templates, and the lack of type information is one of them.
Another problem is that I search for types a lot. Whenever I come to a new piece of code I do a lot of searches for type names and function names to get a rough understanding of how pieces fit together. In Visual Studio I can search for all references of a type, but it is unreliable. I usually just do a plain text search because it is more reliable than using the smarter search. Using auto makes searching for types more difficult, which adds work for people when they want to learn how objects are used.
Which is why I’m much more pragmatic about auto: I use it only if I think that the type information will not be terribly useful for people looking at my code. Meaning mainly for iterators.
“… intimidated by templates, and the lack of type information is one of them.” – nah, really? I don’t think so. I though it was them pointy brackets that every one was afraid of. Seriously I think the lack of type information in templates isn’t a problem, but then I won’t have too much problems with AAA style.
Being pragmatic about it certainly makes sense though, and the toolset argument is always valid. Grep ftw., until google open sources that source code indexer based on Clang. (as mentioned here: https://www.youtube.com/watch?v=NOCElcMcFik )
While at first the grepping argument seems valid, it’s really isn’t and is just giving you a false sense of security when grepping your code.
For example, say you want to find everywhere that user_class is used.
user_class get_foo();
void use_foo(user_class const& c);
void bar() {
auto f = get_foo();
use_foo(f);
}
According to your argument, this is less grepable than not using auto. But, what about this;
void bar() {
use_foo(get_foo()));
}
In both cases you need to grep for all uses of get_foo after grepping for user_class in order to understand how user_class is used. There’s no way around it. So again it seems nothing is lost by using auto.
I have yet to see a valid argument against auto, I really have.
Yeah as I said I only search to get a rough understanding. Just to check where a type ends up at. Which files belong together, that kind of stuff. So in this example the function “use_bar” would have shown up for me. (And then maybe I would have searched for uses of that function, and maybe I wouldn’t have. Depends on how much I want to know)
When I need reliable information for refactoring I usually make a breaking change so that the compiler tells me all the places I have to look at.
Still it’s useful to be able to search. Getting reliable information takes a lot longer than a simple grep, and often it’s not even needed. Still it’s entirely a tools issue. The only reason for this specific part of the recommendation is that our tools are bad. If for some reason they stop being bad soon (fat chance because they’ve had twenty years and still haven’t figured out a reliable search) then I won’t need to use grep. But even then I’d still probably encourage not using auto, because type information is often useful to make code more readable.