I hate the C++ keyword auto

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:

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:

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.

TwitterRedditFacebookShare

24 thoughts on “I hate the C++ keyword auto

  1. e-dog

    I disagree, auto is pretty good at avoiding stupid repetition in C++ like:

    MyNamespace::MyObject *obj=new MyNamespace::MyObject();

    auto obj=new MyNamespace::MyObject();

    Reply
    1. Randy Gaul Post author

      “Avoid stupid repetition in C++” isn’t that C++’s fault that there’s stupid repetition in the first place? It does sound silly to add a keyword to clean up a previous language design flaw (stupid repetition), when this new keyword creates additional language design flaws (unnecessary/costly abstraction).

      Reply
      1. Dexter Miguel

        Sorry, don’t agree with you at all. It’s the same thing in C#.


        List names = new List();
        var names = new List();

        var here is implied to return type List and it avoids me having to specify the type name again. In a for loop, you’d assume that the default type that is being returned is int, so it’s only natural that you’d assume that results.begin() returns an int.

        I think the problem is more with coding style than anything. What does SortByName return, and why does the code look that? I think this is a huge code smell rather than a problem with the auto keyword per se.

        Reply
        1. john

          Furthermore, the most basic feature any IDE has is the ability to tell you what type something is, so you don’t even need to pull up the documentation in a separate window to figure out what the type of x is after var x = Blah.X();. But perhaps the functionality isn’t as good in C++ since it doesn’t have a proper way to use code that’s not in the same file as the one you’re writing.

          Reply
  2. GeorgeJohnFrank

    The “Or better yet, just stop using it at all” is terrible advice.

    1. Places where you *should* use auto

    1. APIs that are documented in terms of “unspecified” types. For example, consider std::bind. What is its return type? You do not know.  If you were to store the return value, you want to do:

    auto result = std::bind(…);

    You might ask, what about using a std::function<whatever> as the type? This is not the same as auto in that expression. You’d incur more overhead than you need to.

    Lambdas are in the same boat as std::bind. The return type is unspecified. Converting to a std::function is more overhead. Using auto is the right thing.

    2. Maintainability of code

    Do you also not like type aliases with typedef or using? What is the advantage of:

    UserToAddress result = f();

    compared to

    std::map<User, Address> result = f();

    Surely the former makes it possible to change f()‘s return value from using std::map to, for example, using std::unordered_map, without having to change all the code that consumes f().

    Equally maintainable:

    auto result = f();

    You try to differentiate “generic” code from non-generic code. Even small segments of code like this can be seen as a form of generic (and thus maintainable) code.

    Reply
    1. Randy Gaul Post author

      1. Sure, if we assume to already be using an abstraction then auto will likely make some sense. In this case I tend to wonder if the assumed abstraction is a good idea or not and reflect along this line of scrutiny, instead of only tacking on the auto keyword.

      2. Abstractions have costs and benefits and each ought to be carefully weighed against one another. If we pick a scenario where an abstraction is assumed to be beneficial despite its cost, then of course using a language construct based around abstraction might have its own cost mitigated (due to the aforementioned assumption).

      Reply
  3. Vittorio Romeo

    Please, stop placing auto in the middle of random every-day code. Or better yet, just stop using it at all.

    Sorry, but I think this is terrible advice.

    auto allows your code to be more generic,  terser and easier to understand.
    Read “Almost always auto” by Herb Sutter.

    I’m all up for data-oriented design and cache-friendliness, but a lot of “DOD advocates” seem to think that modern code and abstractions are directly in constrast with DOD.

    I strongly disagree.

    Modern C++’s “cost-free abstractions” do not hinder the ability to write data-oriented code in any way. If anything, they allow writing more readable and more generic data-oriented code.

    If you want to take this to the extreme, it is possible to have a cost-free abstraction over the way you access data. Check this article out: “C++ encapsulation for Data-Oriented Design”.

    Your blog post makes me think you hate abstraction for some reason. There can be bad abstractions, but I’m strongly confident in the fact that abstraction is inherently a good thing.

     

    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.

    I don’t see how this is different from wrapping algorithm code in a function. If you want to see how it works, go look at the source code for the function. Hopefully you’re not gonna copy-paste the same algorithm code in multiple places just because you want to be as explicit as possible.

    It is the same with auto, classes, and methods. Interested in the implementation details? Go look at the source code or at the documentation.

    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.

    It could be doing anything. Any function call could be doing anything without you knowing.

    Hopefully, you’re writing sensible code and using good libraries. If that’s the case, then this loop is iterating over a container returned by SortByName.

    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    struct Results
    {
        char** entries;
        int entryCount;
    };

    Results results = SortByName( input );

    for ( int i = 0; i < results.entryCount; ++i )
    {
        char* entry = results.entries[ i ];
        // …
    }

    StoreResults( results );

    This is my idea of unmaintainable mess. Non-generic, C-like code, that requires you to change the application code when changing implementation details. Why would you do this? Is it really worth dealing with this kind of verbose and unmaintainable code because you couldn’t bother checking the source code or documentation of classes/functions you’re using?

    What if you figure out a more efficient way of storing entries? What if you want to reuse the loop you’ve just written with another container, maybe a non-random-access one?

    I can’t believe it is not obvious to you how much more generic and convenient the code with auto is.

    Also, an even better and more idiomatic version of this code would make use of C++11-style for-each loops or of a call to std::for_each.

     

    I really like your work and your projects, but I think you’ve got the wrong idea about modern C++ and abstractions.

    They’re not inherently against code readability and data-oriented design. I think that good abstractions can result in more maintainable, more efficient, more readable and more reusable code.

    Generic, safe and reusable code is not only for libraries – application code should also have these qualities.

    I strongly suggest you to watch Bjarne’s latest keynotes:

    CppCon 2014: Bjarne Stroustrup “Make Simple Tasks Simple!”

    CppCon 2015: Bjarne Stroustrup “Writing Good C++14”

    They do a very good job at explaining what C++ is about: simple, generic and extremely efficient code, thanks to cost-free abstractions.

    Reply
    1. Randy Gaul Post author

      Hi Vittorio,

      Please note I didn’t say DOD or Data Oriented Design in my blog post. I’m not advocating or attacking any acronyms. As for “cost-free abstractions”, well I don’t really believe in that sort of thing. In my mind an abstraction always has a cost, and whether or not you care about that cost relative to the benefit is all that matters. If you disagree, that’s OK — it’s probably not something important enough to come to an agreement upon anyway.

      “I don’t see how this is different from wrapping algorithm code in a function. If you want to see how it works, go look at the source code for the function. Hopefully you’re not gonna copy-paste the same algorithm code in multiple places just because you want to be as explicit as possible.”

      I’m assuming by “this” you meant overloaded operators, constructors and destructors, right? In my opinion it’s difficult to catch all possibilities of code generation and code-flow (as in call stack or jumps) at a quick glance, when compared to a regular old function call. When I have to start reasoning about all of these things more information is clogging up my mind when I really just want to solve my problem and move on to the next. It’s just about trying to cut out the cruft and get straight to the point (where the point is how the code solves the given problem). Things that get in the way of me figuring out how code solves the given problem bug me, and auto is one of these things. Sure, some places the abstraction cost of auto is minimal and the benefits are real, I get that like everyone else reading this blog post — I did give some specific examples on this in my post.

      As for copy-pasting code, yes, I like to copy paste specific pieces of code for a variety of reasons, like lowering file dependencies and compile-times. It’s also nice to be able to use ctrl+f within a single self-contained file in many situations.

      Cheers,
      Randy

      Reply
  4. Roger

    How about instead you use proper names? “Results” is a terrible name. So is “i”. That is why you can’t understand that code. Your ‘fix’ is still unreadable – the struct is rarely near the for loop, so you are going to have to go searching anyway to figure out what the hell is going on.

    This is completely readable, IMO:

    for (auto employee : sorted_employees) {
    StoreEmpoyeeInFoo(employee);
    }

     

    Reply
    1. Randy Gaul Post author

      Hey I agree with you, but my personal cynicism goes a little farther; even if “results” is named well as I glance at this code I’ll still be skeptical of the programmer that originally wrote it. I want to know, for a fact, at a glance (if possible) what will happen when this is compiled. I don’t like to rely much on language constructs like auto or C++ foreach, as sort of forces me to “go on a limb” and trust the other programmers to follow conventions. And so what solution we choose becomes preference; you prefer convention and I prefer to have the types laid out in the open.

      Reply
    2. watcher

      Those apparently stupid people defending auto are being paid for this. auto and other C++11, C++14 stupid modifications and all the marketing surrounding and stimulating its use have the objectives of make the code difficult to read and divide people making hard for non paid developers to work together.

      Reply
  5. Oldcat

    Sorry, but your ‘cleaned up’ example has just as many issues as the ‘auto’ case.  The results structure has totally unknown ownership rights and format rules that can only be known from other information not present.  If in the first example the function returns a standard, or standard conforming container class, then the rules are more clear.  The begin() / end() convention is about as clear in both cases, as for a well-formed container class the convention that these are iterating over the container is well-known.  I’d give the edge to the first case, there.

    I think the first auto is probably too cute in the first example, as it would be a help to specify what container is being used in the remainder of the code unless it is just impossible to discover the type.  A comment describing what container is expected there is warranted and would give the reader somewhere to look for the rules in both cases.

    Reply
  6. Aivars

    I’m with Vittorio Romeo, your non-auto is an example of bad and hard-to-maintain code. Pointers to pointers? Who allocates it? Who should free it? Why char * (what if inputs are std:strings)?

    Reply
  7. gpfault

    After you write enough C++, you will stop bothering yourself with questions like ” What does that = operator do?” and “what is .begin, what is i?” (because the answers are obvious in the context that you presented). You will, however, wish to never again have to write things like this:

    std::vector<LongAssClassName::NestedType>::iterator i = container.begin(); // typing out obvious types makes me sad

    or this:

    std::unique_ptr<AnotherLongAssClassName> object = std::make_unique<AnotherLongAssClassName>(); // I can't believe I have to type the type name twice, this is such bullshit

    auto will save you from an early onset of carpal tunnel syndromme :)

    Reply
    1. Josh

      I could not agree any more! Seldom am I too lazy to go look up a return type compared to the abhorrence that fills me when I have to write something like: std::pair<std::unordered_map<unsigned long long, std::map>::iterator, bool> foo = m_Map.emplace(key, value);

      Reply
  8. Simon Lundmark

    Well written pal,

     

    I agree with you – and I advocate a more C99-usage of C++ – maybe add classes and keep usage of them to a minimal.

    Hope to catch you on GDC again.

     

    Cheers,

    Simon

    Reply
  9. DC MCFN

    People who like auto are just lazy programmers. They don’t know how much it sucks because they never read anyone else’s code. You know what, I really wonder if they read their own code.

    Reply
    1. Akiva

      Yeah, I am with you on that.

      There are performance benefits to Auto, at least this is what I am told (And have no reason to disbelieve this), but I would rank the principle of

      “Code is read more than it is written”

      to be more important.

      I have never come across a situation, at least in Qt, where I wish the Auto Keyword would clean something up.

      Reply
  10. petke

    When i first tried auto, I also found it bad. But I have since change my mind.

    Good code is in my opinion is

    Easy to change
    Easy to read
    Easy to write

    Auto makes code more difficult to read, at least on paper. But most people read code in an IDE that can help them see what type auto is substituted for. (In Visual Studio for instance, you just put the mouse pointer over the “auto” keyword and you see the actual type).

    Auto also makes code easier to write, but most importantly it makes code easier to change.

    Imagine a commonly used library function of yours, that is used by a lot of user code. Changing the return type of that library function is difficult, as you need to change all the user code also. Sometimes you do not own all the user code, and so you cant change the return type of that function even if we wanted too.

    If we instead use auto in all user code, then we only create a (code time) dependency to the interface, but not to the type of that library function. The owner of the library function can change the return type of that function, and users just need to recompile. The less dependencies and the weaker they are, the better and easier it is to change in the future.

    For this reason (and others), auto is worth it.

     

    Reply
  11. Finalspace

    I fully agree with this article. At start i liked auto very much and used it all over the place, but after thinking about – it was actually only useful for apis poor designed, like for example the std library:

    Using auto helps, but the api is still fail – and this is a very simple example:

    Also this is just wrong – helps nobody to understand your code:

     

    Conclusion: Use auto for bad designed apis only!

     

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *