Warning: rant post incoming!
I’m not really sure what other programmers are thinking about or focusing on when they read code, but personally I’m thinking mostly about the data being operated on. One of the first things I like to figure out when reading new code is if any C++ constructors, destructors, or other features that generate code are being used with a current data type.
If a piece of code is operating on only integers usually it becomes easy to have a grounded concept in what kind of assembly is to be generated. The same goes for code operating on just floats, or in general just POD data. The moment C++ features are used that generate large amounts of code at compile-time “surprises” can pop out in the form of constructor code, destructor code, implicit conversions, or entire overloaded operators.
This bugs the crap out of me. When I look at code I rarely want to know only what the algorithm does, and instead need to also know how the algorithm operates on given data. If I can’t immediately know what the hell is going inside of a piece of code the entire code is meaningless to me. Here’s an example:
auto results = SortByName( input );
for ( auto i = results.begin( ); i < results.end( ); ++i )
StoreResults( results );
Please, tell me what the hell this for loop is doing. What does that = operator do? Is there a possible implicit cast when we pass results to the StoreResults function? What is .begin, what is i, and how might the ++ operator behave. None of the questions can be answered [to my specific and cynical way of thinking about C++] since this code is operating on purely opaque data. What benefit is the auto keyword giving here? In my opinion absolutely none.
Take a non-auto example:
Results results = SortByName( input );
for ( int i = 0; i < results.entryCount; ++i )
char* entry = results.entries[ i ];
StoreResults( results );
In the above code it is painfully obvious that the only opaque pieces of code are functions, and the functions are easy to locate at a glance. No unanticipated code can be generated anywhere, and whenever the user wants to lookup the Results struct to make sure there are no “C++ loose ends” in terms of code-flow a simple header lookup to find the Results declaration is all that’s needed. I enjoy when code is generated in a predictable way, not in a hidden or hard to reason about way. The faster I can reason about what this code will do at run-time the faster I can forget about it an move onto something else more important.
Code with unnecessary abstractions bugs me since it takes extra time to understand (if an understanding can even be reached), and that cost better come with a great benefit otherwise the abstraction nets a negative impact. In the first example it’s not even possible to lookup what kind of data the results variable represented without first looking up the SortByName function and checking the type of the returning parameter.
However, using auto inside of (or alongside) a macro or template makes perfect sense. Both macros and templates aim at providing the user with some means of code generation, and are by-design opaque constructs. Using auto inside of an opaque construct can result in an increase in the effectiveness of the overall abstraction, without creating unnecessary abstraction-cost. For example, hiding a giant templated typename, or creating more versatile debugging macros are good use-cases for the auto keyword. In these scenarios we can think of the auto keyword as an abstraction with an abstraction-cost, but this cost is side-stepped by the previously assumed abstraction-costs of templates/macros.
Please, stop placing auto in the middle of random every-day code. Or better yet, just stop using it at all.