GotW #94 Solution: AAA Style (Almost Always Auto)

Toward correct-by-default, efficient-by-default, and pitfall-free-by-default variable declarations, using “AAA style”… where “triple-A” is both a mnemonic and an evaluation of its value.

Problem

JG Questions

1. What does this code do? What would be a good name for some_function?

template<class Container, class Value>
void some_function( Container& c, const Value& v ) {
    if( find(begin(c), end(c), v) == end(c) )
        c.emplace_back(v); 
    assert( !c.empty() );
}

2. What does “write code against interfaces, not implementations” mean, and why is it generally beneficial?

Guru Questions

3. What are some popular concerns about using auto to declare variables? Are they valid? Discuss.

4. When declaring a new local variable x, what advantages are there to declaring it using auto and one of the two following syntaxes:

(a) auto x = init; when you don’t need to commit to a specific type? (Note: The expression init might include calling a helper that performs partial type adjustment, such as as_signed, while still not committing to a specific type.)

(b) auto x = type{ init }; when you do want to commit to a specific type by naming a type?

List as many as you can. (Hint: Look back to GotW #93.)

5. Explain how using the style suggested in #4 is consistent with, or actively leverages, the following other C++ features:

(a) Heap allocation syntax.

(b) Literal suffixes, including user-defined literal operators.

(c) Named lambda syntax.

(d) Function declarations.

(e) Template alias declarations.

6. Are there any cases where it is not possible to use the style in #4 to declare all local variables?

Solution

1. What does this code do? What would be a good name for some_function?

template<class Container, class Value>
void append_unique( Container& c, const Value& v ) {
    if( find(begin(c), end(c), v) == end(c) )
        c.emplace_back(v); 
    assert( !c.empty() );
}

Let’s call this function append_unique. First, it checks to see whether the value v is already in the container. If not, it appends it at the end. Finally, it asserts that c is not empty, since by now it must contain one copy of the value v.

You probably thought this question was fairly easy.

Maybe too easy.

If so, good. That’s the point of the example. Hold the thought, and we’ll come back to this in Question 3.

2. What does “write code against interfaces, not implementations” mean, and why is it generally beneficial?

It means we should care principally about “what,” not “how.” This separation of concerns applies at all levels in high-quality modern software—hiding code, hiding data, and hiding type. Each increases encapsulation and reduces coupling, which are essential for large-scale and robust software.

Please indulge a little repetition in the following paragraphs. It’s there to make a point about similarity.

Hiding code. With the invention of separately compiled functions and structured programming, we gained “encapsulation to hide code.” The caller knows the signature only—the function’s internal code is not his concern and not accessible programmatically, even if the function is inline and the body happens to be visible in source code. We try hard not to inadvertently leak implementation details, such as internal data structure types. The point is that the caller does not, and should not, commit to knowledge of the current internal code; if he did, it would create interdependencies and make separately compiled libraries impossible.

Hiding data (and code). With object oriented styles (OO), we gained two new manifestations of this separation. First, we got “more encapsulation to hide both code and data.” The caller knows the class name, bases, and member function signatures only—the class’s internal data and internal code are hidden and not accessible programmatically, even though the private class members are lexically visible in the class definition and inline function bodies may also be visible. (In turn, dynamic libraries and the potential future-C++ modules work aim to accomplish the same thing at a still larger scale.) Again we try hard not to inadvertently leak implementation details, and again the point is that the caller does not, and should not, commit to knowledge of the current internal data or code, which would make the class difficult to ever change or to ship on its own as a library.

Hiding type (run-time polymorphism). Second, OO also gave us “separation of interfaces to hide type.” A base class or interface can delegate work to a concrete derived implementation via virtual functions. Now the interface the caller sees and the implementation are actually different types, and the caller knows the base type only—he doesn’t know or care about the concrete type, including even its size. The point, once again, is that the caller does not, and should not, commit to a single concrete type, which would make the caller’s code less general and less able to be reused with new types.

Hiding type (compile-time polymorphism). With templates, we gained a new compile-time form of this separation—and it’s still “separation of interfaces to hide type.” The caller knows an ad-hoc “duck typed” set of operations he wants to perform using a type, and any type that supports those operations will do just fine. The contemplated future C++ concepts feature will allow making this stricter and less ad-hoc, but still avoids committing to a concrete type at all. The whole point is still is that the caller does not, and should not, commit to a single concrete type, which would make the caller’s code less generic and less able to be reused with new types.

3. What are some popular concerns about using auto to declare variables? Are they valid? Discuss.

In many languages, not just C++, there are several reasons people commonly give for why they are reluctant to use auto to declare variables (or the equivalent in another language, such as var or let). We could summarize them as: laziness, commitment, and readability. Let’s take them in order.

Laziness and commitment

First, laziness: One common concern is that “writing auto to declare a variable is primarily about saving typing.” However, this is just a misunderstanding of auto. As we saw in GotW #92 and #93 and will see again below, the main reasons to declare variables using auto are for correctness, performance, maintainability, and robustness—and, yes, convenience, but that’s in last place on the list.

Guideline: Remember that preferring auto variables is motivated primarily by correctness, performance, maintainability, and robustness—and only lastly about typing convenience.

Second, commitment: “But in some cases I do want to commit to a specific type, not automatically deduce it, so I can’t use auto.” It’s true that sometimes you do want to commit to a specific type, but you can still use auto. As demonstrated in GotW #92 and #93, not only can you still write declarations of the form auto x = type{ init }; (instead of type x{init};) to commit to a specific type, but there are good reasons for doing so, such as that saying auto means you can’t possibly forget to initialize the variable.

Guideline: Consider declaring local variables auto x = type{ expr }; when you do want to explicitly commit to a type. It is self-documenting to show that the code is explicitly requesting a conversion, it guarantees the variable will be initialized, and it won’t allow an accidental implicit narrowing conversion. Only when you do want explicit narrowing, use ( ) instead of { }.

(Un)readability?

The third and most common argument concerns readability: “My code gets unreadable quickly when I don’t know what exact type my variable is without hunting around to see what that function or expression returns, so I can’t just use auto all the time.” There is truth to this, including losing the ability to search for occurrences of specific types when using the non-typed syntax auto x = expr; in 4(a) below, so this appears at first to be a strong argument. And it’s true that any feature can be overused. However, I think this argument is actually weaker than it first seems for four reasons, two minor and two major.

The two minor counterarguments are:

  • The “can’t use auto” part isn’t actually true, because as we just saw above you can be explicit about your type and still use auto, with good benefit.
  • The argument doesn’t apply when you’re using an IDE, because you can always tell the exact type, for example by hovering over the variable. Granted, this mitigation goes away when you leave the IDE, such as if you print the code.

But we should focus on the two major counterarguments:

  • It reflects a bias to code against implementations, not interfaces. Overcommitting to explicit types makes code less generic and more interdependent, and therefore more brittle and limited. It runs counter to the excellent reasons to “write code against interfaces, not implementations” we saw in Question 2.
  • We (meaning you) already ignore actual types all the time…

“… Wait, what? I do not ignore types all the time,” someone might say. Actually, not only do you do it, but you’re so comfortable and cavalier about it that you may not even realize you’re doing it. Let’s go back to that code in Question 1:

template<class Container, class Value>
void append_unique( Container& c, const Value& v ) {
    if( find(begin(c), end(c), v) == end(c) )
        c.emplace_back(v); 
    assert( !c.empty() );
}

Quick quiz: How many specific types are mentioned in that function? Name as many as you can.

Take a moment to consider that before reading on…

… We can see pretty quickly that the answer is a nice round number: Zero. Zilch. (Pedantic mode: Yes, there’s void, but I’m going to declare that void doesn’t count because it’s to denote “no type,” it’s not a meaningful type.)

Not a single specific type appears anywhere in this code, and the lack of exact types makes it much more powerful and doesn’t significantly harm its readability. Like most people, you probably thought Question 1 felt “easy” when we did it in isolation. Granted, this is generic code, and not all your code will be templates—but the point is that the code isn’t unreadable even though it doesn’t mention specific types, and in fact auto gives you the ability to write generic code even when not writing a template.

So starting with the cases illustrated in this short example, let’s consider some places where we routinely ignore exact types. First, function template parameters:

  • What exact type is Container? We have no idea, and that’s great… anything we can call begin, end, emplace_back and empty on and otherwise use as needed by this code will do just fine. In fact, we’re glad we don’t know anything about the exact type, because it means we’re following the Open/Closed Principle and staying open for extension— this append_unique will work fine with a type that won’t be written until years from now. Interestingly, the concepts feature currently being proposed for ISO C++ to express template parameter constraints doesn’t change how this works at all, it only makes it more convenient to express and check the requirements. Note how much more powerful this is compared to OO style frameworks: In OO frameworks where containers have to inherit from a base class or interface, that’s already inducing coupling and limiting the ability to just plug in and use arbitrary suitable types. It is important that we can know nothing at all about the type here besides its necessary interface, not even restricting it by as much as limiting it to types in a particular inheritance hierarchy. We should strongly resist compromising this wonderful and powerful “strictly typed but loosely coupled” genericity.
  • What exact type is Value? Again, we don’t know, and we don’t want to know… anything we can pass to find and emplace_back is just dandy. At this point some of you may be thinking: “Oh yes we know what type it is, it’s the container’s value type!” No, it doesn’t have to be that, it just has to be convertible, and that’s important. For example, we want vector<string> vec; append_unique(vec, “xyzzy”); to work, and “xyzzy” is a const char[6], not a string.

Second, function return values:

  • What type does find return? Some iterator type, the same as begin(c) coughed up, but we don’t know specifically what type it is just from reading this code, and it doesn’t matter. We can look up the signature if we’re feeling really curious, but nobody bothers doing that because anything that’s comparable to end(c) will do.
  • What type does empty return? We don’t even think twice about it. Something testable like a bool… we don’t care much what exactly as long as we can “not” it.

Third, many function parameters:

  • What specific type does emplace_back take? Don’t know; might be the same as v, might not. Really don’t care. Can we pass v to it? Yes? Groovy.

And that’s just in this example. We routinely and desirably ignore types in many other places, such as:

  • Fourth, any temporary object: We never get to name the object, much less name its type, and we may know what the type is but we don’t care about actually spelling out either name in our code.
  • Fifth, any use of a base class: We don’t know the dynamic concrete type we’re actually using, and that’s a benefit, not a bug.
  • Sixth, any call to a virtual function: Ditto; plus on top of that if the virtual function return type itself could also be covariant for another layer of “we don’t know the dynamic concrete type” since in the presence of covariance we don’t know what type we’re actually getting back.
  • Seventh, any use of function<>, bind, or other type erasure: Just think about how little we actually know, and how happy it makes us. For example, given a function<int(string)>, not only don’t we know what specific function or object it’s bound to, we don’t even know that thing’s signature—it might not actually even take a string or return an int, because conversions are allowed in both directions, so it only has to take something a string can be converted to, and return something that can be converted to an int. All we know is that it’s something that we can invoke with a string and that gives us back something we can use as an int. Ignorance is bliss.
  • Eighth, Any use of a C++14 generic lambda function: A generic lambda just means the function call operator is a template, after all, and like any function template it gets stamped out individually for whatever actual argument types you pass each time you use it.

There are probably more.

Although lack of commitment may be a bad thing in other areas of life, not committing to a specific type is often desirable by default in reusable code.

4. When declaring a new local variable x, what advantages are there to declaring it using auto and one of the two following syntaxes:

Let’s consider the base case first, which has by far the strongest arguments in its favor and is gaining quite a bit of traction in the C++ community.

(a) auto x = init; when you don’t need to commit to a specific type?

GotW #93 offered many concrete examples to support habitually declaring local variables using auto x = expr; when you don’t need to explicitly commit to a type. The advantages include:

  • It guarantees the variable will be initialized. Uninitialized variables are impossible because once you start by saying auto the = is required and cannot be forgotten.
  • It is efficient by default and guarantees that no implicit conversions (including narrowing conversions), temporary objects, or wrapper indirections will occur. In particular, prefer using auto instead of function<> to name lambdas unless you need the type erasure and indirection.
  • It guarantees that you will use the correct exact type now.
  • It guarantees that you will continue to use the correct exact type under maintenance as the code changes, and the variable’s type automatically tracks other functions’ and expressions’ types unless you explicitly said otherwise.
  • It is the simplest way to portably spell the implementation-specific type of arithmetic operations on built-in types, which vary by platform, and ensure that you cannot accidentally get lossy narrowing conversions when storing the result.
  • It is the only good option for hard-to-spell and impossible-to-spell types such as lambdas, binders, detail:: helpers, and template helpers (including expression templates when they should stay unevaluated for performance), short of resorting to repetitive decltype expressions or more-expensive indirections like function<>.
  • It is more symmetric and consistent with other parts of modern C++ (see Question 5).
  • And yes, it is just generally simpler and less typing.

See GotW #93 for concrete examples of these cases, where using auto helps eliminate correctness bugs, performance bugs, and silently nonportable code.

As noted in the questions, the expression init might include calling a helper that performs partial type adjustment, such as as_signed, while still not committing to a specific type. As shown in GotW #93, prefer to use auto x = as_signed(integer_expr); or auto x = as_unsigned(integer_expr); to store the result of an integer computation that should be signed or unsigned—these should be viewed as “casts that preserve width,” so we are not casting to a specific type but rather casting an attribute of the type while correctly preserving the other basic characteristics of the type, notably by not forcing it to commit to a particular size.

Using auto together with as_signed or as_unsigned makes code more portable: the variable will both be large enough (thanks to auto) and preserve the required signedness on all platforms. Note that signed/unsigned conversions within integer_expr may still occur and so you may need additional finer-grained as_signed/as_unsigned casts within the expression for full portability.

(b) auto x = type{ init }; when you do want to commit to a specific type by naming a type?

This is the explicitly typed form, and it still has advantages but they are not as clearly strong as implicitly typed form. The jury is still out on whether to recommend this one wholesale, as we’re still trying it out, but it does offer some advantages and I suggest you try it out for a while and see if it works well for you.

So here’s the recommendation to consider trying out for yourself: Consider declaring local variables auto x = type{ expr }; when you do want to explicitly commit to a type. (Only when you do want to allow explicit narrowing, use ( ) instead of { }.) The advantages of this typed auto declaration style include:

  • It guarantees the variable will be initialized; you can’t forget.
  • It is self-documenting to show that the code is explicitly requesting a conversion.
  • It won’t allow an accidental implicit narrowing conversion.
  • It is more symmetric and consistent, both with the basic auto x = init; form and with other parts of C++…

… which brings us to Question 5.

5. Explain how using the style suggested in #4 is consistent with, or actively leverages, the following other C++ features:

Let’s start off this question with some side-by-side examples that give us a taste of the symmetry we gain when we habitually declare variables using modern auto style. Starting with two examples where we don’t need to commit to a type and then two where we do, we see that the right-hand style is not only more robust and maintainable for the reasons already given, but also arguably cleaner and more regular with the type consistently on the right when it is mentioned:

// Classic C++ declaration order     // Modern C++ style

const char* s = "Hello";             auto s = "Hello";
widget w = get_widget();             auto w = get_widget();

employee e{ empid };                 auto e = employee{ empid };
widget w{ 12, 34 };                  auto w = widget{ 12, 34 };

Now consider the (dare we say elegant) symmetry with each of the following.

(a) Heap allocation syntax.

When allocating heap variables, did you notice that the type name is already on the right naturally anyway? And since it’s there, we don’t want to have to repeat it. (I’ll show the raw “new” form for completeness, but prefer make_unique and make_shared in that order for allocation in modern code, resorting to raw new only well-encapsulated inside the implementation of low-level data structures.)

// Classic C++ declaration order     // Modern C++ style

widget* w = new widget{};            /* auto w = new widget{}; */
unique_ptr<widget> w                 auto w = make_unique<widget>();
  = make_unique<widget>();

(b) Literal suffixes, including user-defined literal operators.

Using auto declaration style doesn’t merely work naturally with built-in literal suffixes like ul for unsigned long, plus user-defined literals including standard ones now in draft C++14, but it actively encourages using them:

// Classic C++ declaration order     // Modern C++ style

int x = 42;                          auto x = 42;
float x = 42.;                       auto x = 42.f;
unsigned long x = 42;                auto x = 42ul;
std::string x = "42";                auto x = "42"s;   // C++14
chrono::nanoseconds x{ 42 };         auto x = 42ns;    // C++14

Based on the examples so far, which do you think is more regular? But wait, there’s more…

(c) Named lambda syntax.
(d) Function declarations.

Lambdas have unutterable types, and auto is the best way to capture them exactly and efficiently. But because their declarations are now so similar, let’s consider lambdas and (other) functions together, and in the last two lines of this example also use C++14 return type deduction:

// Classic C++ declaration order     // Modern C++ style

int f( double );                     auto f (double) -> int;
…                                    auto f (double) { /*...*/ };
…                                    auto f = [=](double) { /*...*/ };

(e) Template alias declarations.

Modern C++ frees us from the tyranny of un-template-able typedef:

// Classic C++ workaround            // Modern C++ style

typedef set<string> dict;            using dict = set<string>;

template<class T> struct myvec {     template<class T>
  typedef vector<T,myalloc> type;    using myvec = vector<T,myalloc>;
};

An observation

Have you noticed that the C++ world is moving to a left-to-right declaration style everywhere, of the form

category name = type and/or initializer ;

where “category” can be auto or using?

Take a moment to re-skim the two columns of examples above. Even ignoring correctness and performance advantages, do you find the right-hand column to be most consistent, and most readable?

6. Are there any cases where it is not possible to use the style in #4 to declare all local variables?

There is one case I know of where this style cannot be followed, and it applies to the type-specific auto x = type{ init }; form. In that form, type has to be moveable (even though the move operation will be routinely elided by compilers), so these won’t work:

auto lock = lock_guard<mutex>{ m };  // error, not moveable
auto ai   = atomic<int>{};           // error, not moveable

(Aside: For at least some of these cases, an argument could be made that this is actually more of a defect in the type itself, in particular that perhaps atomic<int> should be moveable.)

Having said that, there are three other cases I know of that you might encounter that may at first look like they don’t work with this auto style, but actually do. Let’s consider those for completeness.

First, the basic form auto x = init; will exactly capture an initializer_list or a proxy type, such as an expression template. This is a feature, not a bug, because you have a convenient way to spell both “capture the list or proxy” and “resolve the computation” depending which you mean, and the default syntax goes to the more efficient one: If you want to efficiently capture the list or proxy, use the basic form which gives you performance by default, and if you mean to force the proxy to resolve the computation, specify the explicit type to ask for the conversion you want. For example:

auto i1 = { 1 };                       // initializer_list<int>
auto i2 = 1;                           // int

auto a = matrix{...}, b = matrix{...}; // some type that does lazy eval
auto ab = a * b;                       // to capture the lazy-eval proxy
auto c = matrix{ a * b };              // to force computation

Second, here is a rare case that you may discover now that we have auto: Due to the mechanics of the C++ grammar, you can’t legally write a multi-word type like long long or class widget in the place where type goes in the auto x = type{ init }; form. However, note that this affects only those two cases:

  • The multi-word built-in types like long long, where you’re better off anyway writing a known-width type alias or using a literal.
  • Elaborated type specifiers like class widget, where the “class” part is already redundant. The “class widget” syntax is allowed as a compatibility holdover from C which liked seeing struct widget everywhere unless you typedef‘d the struct part away.

So just avoid the multi-word form and use the better alternative instead:

auto x = long long{ 42 };            // error
auto x = int64_t{ 42 };              // ok, better 
auto x = 42LL;                       // ok, better 

auto y = class X{1,2,3};             // error
auto y = X{1,2,3};                   // ok

Summary

We already ignore explicit and exact types much of the time, including with temporary objects, virtual functions, templates, and more. This is a feature, not a bug, because it makes our code less tightly coupled, and more generic, flexible, reusable, and future-proof.

Declaring variables using auto, whether or not we want to commit to a type, offers advantages for correctness, performance, maintainability, and robustness, as well as typing convenience. Furthermore, it is an example of how the C++ world is moving to a left-to-right declaration style everywhere, of the form

category name = type and/or initializer ;

where “category” can be auto or using, and we can get not only correctness and performance but also consistency benefits by using the style to consistently declare local variables (including using literals and user-defined literals), function declarations, named lambdas, aliases, template aliases, and more.

Acknowledgments

Thanks in particular to Scott Meyers and Andrei Alexandrescu for their time and insights in reviewing and discussing drafts of this material. Both helped generate candidate names for this idiom; it was Alexandrescu who suggested the name “AAA (almost always auto)” which I merged with the best names I’d thought of to that point (“auto style” or “auto (+type) style”) to get “AAA Style (almost always auto).” Thanks also to the following for their feedback to improve this article: Adrian, avjewe, mttpd, ned, zadecn, noniussenior, Marcel Wid, J Guy Davidson, Mark Garcia, Jonathan Wakely, “x y.”

67 thoughts on “GotW #94 Solution: AAA Style (Almost Always Auto)

  1. @herb: Perhaps this may be irrelevant here (Sorry!), I couldn’t find any other relevant place to comment on this. When will the GotWs 8 – 88 be available? I read this article series with in the past few days and learned a lot about C++ !! Hope the other GotWs will be available soon. Thanks for your great work.

  2. @x y: I think I had in mind that the auto version deduced an array, which it doesn’t. If I had something else in mind, I’ve forgotten it now, so I’ve removed that phrase and added your moniker to the Acknowledgments. Thanks for the feedback!

  3. I am referring to this:

    auto a = matrix{…}, b = matrix{…}; // some type that does lazy eval
    auto ab = a * b; // to capture the lazy-eval proxy
    auto c = matrix{ a * b }; // to force computation

    I am surprised, that nobody pointed out, that the third line is a *hidden* static_cast. Consider:

    auto a = matrix{…};
    auto x = 3;
    auto da = a.size();
    auto c = matrix{ x * da }; // oops, ment { x * a }

    In this example, the code will compile, assuming matrix has an explicit constructor matrix(size_t). In contrast, the following uses only implicit casting, so the error will be caught by the compiler.

    matrix c = x * da;

    I think it is good practice to use the weakest cast that is suitable. Using auto in this example not only conflicts with this, but also hides the static_cast.

  4. I have tried to use AAA for a while and cannot say I feel entirely comfortable with it. I am leaning towards using

    auto

    only in certain situations, as it otherwise does reduce my code’s readability. At least in my eyes. Then again, I think it is unfair to judge this feature as long as it has not been used widely, in huge projects, over the course of many years. I think it will show its true virtues (or the lack thereof) when it will be subject to code maintenance.

    One fear I have is that AAA style may inadvertently cause a revival of Hungarian Notation. It would not surprise me if many people started to actually write stuff like

    auto intCount = 0;

    or

    auto strName = GetName();

    (or

    auto dwRet = SomeWinApiFunction();

    :)), completely defeating the purpose of

    auto

    in the first place.

  5. I’m wondering what downsides there are to using const auto& wherever const auto would be appropriate (except in cases where you specifically want to copy the right hand side). Like auto in general, this protects you in the event that the right hand side changes.

    For example, you might write be tempted to write…

    const auto x = widget.foo();

    …if you know that widget.foo() returns a value, because using const auto& here is superfluous. But if widget.foo() is changed to return a reference, then you’d be making a copy/move where you probably didn’t need one. Since you wanted a const object, it is unlikely that you wanted a copy anyway.

    Another example — you might decide to change…

    const auto x = get_raw_ptr();

    …to…

    const auto x = *get_raw_ptr();

    …without changing to const auto&. Now you’ve inadvertently copied/moved the right hand side. This type of change might not be likely, and will typically require modification to surrounding code, giving you an opportunity to re-evaluate your const auto declaration. Still, it could slip by.

    Instead, if you had changed…

    const auto& x = get_raw_ptr();

    …to…

    const auto& x = *get_raw_ptr();

    …then no inadvertent copy would be introduced.

    (The above example doesn’t make sense if the function is get_unique_ptr(), because const auto x = *get_unique_ptr() is probably wrong (unnecessary copy/move), and const auto& x = *get_unique_ptr(); is always wrong (referring to an object that is about to be destroyed).)

    Are these good enough reasons to prefer const auto& even where const auto makes sense? Are there cases where using const auto& in favor of const auto would backfire?

  6. @Richard:

    Firstly, I’m not concerned whether the library is efficiently returning types; I’m concerned whether any given variable that is being passed round through assignment, parameters, etc is intrinsic type, object-by-value, reference, smart pointer, or raw pointer. Without understanding which of these you’re dealing with, your understanding of what’s going on is necessarily limited.

    Secondly, you are not understanding me correctly. I am not and have never been talking about

    auto x = y();
    

    and conversion operators.

    I am talking about understanding the more fundamental

    auto x = y;
    

    and knowing whether your assignment operator is plausibly overloaded and thus may throw.

    Finally, when you are working with an existing codebase, you cannot guarantee that it is exception-safe, or even that RAII was used. Both the age of the codebase and the understanding of the programmer factor into these. Incidentally, RAII alone does not guarantee strong exception-safety (you also need to consider patterns such as copy-and-swap).

  7. @peter
    Unfortunately every new feature is something else open to abuse/misuse, not totally sure a lot of new features are either warranted or sensible, auto being one of them, just more to go wrong and less compatible c++ code! Perhaps the working committees need to perhaps consider reducing the breadth of C++ rather than making it even more complex?

    All that was really required was a good auto pointer, parallel/task support and some extras in STL!

    Have to admit did like the idea of auto, but now realising it’s folly in most circumstances, it’s like maintaining with a blindfold on or at least running around to see what’s going on! Your input has made me think!

  8. @Peter I hear you. I have been in the business for a long time too and I have seen my fair share of horrid upstream interfaces.
    It seems to me that you are concerned about two things:
    1) is my library efficiently returning types? Can I increase the efficiency in client code?
    2) will it throw exceptions?

    I would argue that (1) is intractable. Reading the documentation (or at least the interface declarations) of the library will tell you what types are being returned, whether they are intrinsic types, references or copies.
    At this point whether you write auto or name a type in the client code is irrelevant unless you are planning to perform a conversion on the returned type. If I understand you correctly, you are considering a case like this:

      // declaration:
      thing get_a_thing();
    
      // user code
      part_of_a_thing mything = get_a_thing();  // conversion constructor called
    

    this can be expressed succinctly using auto

      // user code
      auto mything = part_of_a_thing { get_a_thing() };
    

    In fact, I would argue that the auto version is explicitly stating that a conversion constructor is being called and is therefore more easily understood and debugged.

    regarding point 2, I am not sure I understand there to be a problem. The interface of get_a_thing() will indicate wether or not exceptions can be thrown. In any case, all your user code will be written in terms of RAII so it will at least be exception-safe. Any objects you use from the _thing_ library that do not use RAII can be wrapped in a unique_ptr (or shared_ptr) with a custom deleter, so they will be exception safe too.

    example:

      // declarations
      thing* make_thing();
      void dispose_of_thing(thing*);
    
      // my user code
      {
        auto mything = std::shared_ptr<thing> { make_a_thing(), dispose_of_thing };
        // do things that may throw exceptions...
        ...
        // dispose_of_thing() called here upon destruction of the shared_ptr
      }
    
      // or maybe this is better
      // define my own safe factory function to wrap the unsafe library:
      std::shared_ptr<thing> my_safe_make_a_thing()  // lack of noexcept indicates that this may throw
      { 
        return { make_a_thing(), dispose_of_thing 
        }; 
      }
    
      // my user code:
      auto mything = my_safe_make_a_thing();
    
  9. @Richard:

    We appear to be talking fundamentally at cross purposes.

    I am not discussing the choices made by the person who wrote the code. I am discussing a much more common scenario: the developer who has to read and understand the code.

    By the time you are maintaining the code, it is in some senses too late, as the dumb decisions have already been made. Certainly the compiler will try to ensure that

    auto x = y();
    

    is efficient. (Though, please note I was *not* discussing the efficiency of that line at any point, only the line subsequent.) The compiler will not however be able to work out that the entire design is unreasonable, that the whole object is being passed back when subsequent use of it relies only on reading one flag. It will not warn you on occasions when an aliased shared pointer is being used as a parameter unsafely. There are many, many situations where the code you are reading is suboptimal, and you need to work out whether it is okay to be a little suboptimal here, or whether it is suboptimal in a way likely to prevent your new additions scaling well, or whether it is actually also unsafe. And, importantly, auto will do very little to help that code be more optimal. Certainly, it may stop some dumb conversions being made, but these represent only a very tiny fraction of bugs – and arguably, when the programmer stops thinking about types very hard, it frees them up to more easily make mistakes like writing a for loop that doesn’t terminate because of overflow errors.

    The example I actually gave you:

    auto previous = result;
    

    could well throw. If it’s an intrinsic type it won’t. If it’s a type you know well, you can probably hazard a guess as to whether it’s likely to. If it is a type you haven’t encountered before in this codebase, you either hope it doesn’t and hope you don’t get bitten later, or invest the time in finding out. But whether it throws or not may well change whether the function is strongly exception safe, or even exception safe at all. It’s important.

    C++ is a language of a myriad subtleties. Auto disguises some of them. In any context save toy problems, this is an issue. It is, I contend, hugely unwise to recommend its use by default. There are obvious situations where it can aid clarity. There are also situations where it will obscure useful details. Within the next ten years, I suspect we’ll all have had our fill of the latter.

  10. Let’s assume we agree on the following principles:
    1. early optimisation is evil (don’t make programs untidy)
    2. early pessimisation is evil (don’t prevent the compiler from doing RVO etc)
    3. write for clarity, good optimisation begins with logically efficient algorithms

    I would argue that a software team which adheres to these principles will rarely be able to do better than the compiler for code such as

        auto x = y();
    

    Since the compiler will have optimised the construction of the return value by constructing it in-place or calling the move constructor (see 2).

    y() is free to return whichever type is most logically appropriate, and there will be no performance cost.

  11. @red1939:

    I would entirely agree that it is possible that “Speed previous = result;” is actually surprisingly efficient, particularly given the brutalisations made possible with typedef. However, when you see float, you have a wealth of information to reason with. It is fast to copy. You are likely to be able to assume assignment is atomic, depending on your architecture. Numerical comparisons done with it require a little care. There’s much more.

    Beyond that, we can reason about standard types. Shared pointers, std::strings, container classes – we feel able to make certain expectations when we see them. Are you not nervous to see someone has implemented their own linked list class precisely because it is unknown?

    The C++ language already makes it hard to reason about a wealth of things, and you will often be in unknown territory. Often this doesn’t matter. Sometimes it does. Features which can easily be used to hide the fact you are actually in known territory are potentially dangerous features, and features that should be used with caution.

    Thinking you understand an algorithm without understanding the data representation is, I submit, potentially folly. Algorithms that are excellent when you’re working with a vector are idiocy when working with a linked list. And, if you’re at the point you don’t care about performance, there’s no point in spending your time on C++; go off and work in Python and make your life easier.

    I happen to agree that absurdly long types with nested namespaces are clumsy, hard on both writer and reader, and there are places where auto can only improve readability. (I suspect that the problem here originates with the way such namespaces grow up, but we are often in a position where we have to live with it now.) My concern is that there are also a great many places where it will be practically hazardous, where types being returned are not as the reader expects, and it will prove to be a particularly devastating weapon in the hands of the novice and the lazy coder.

  12. @Peter Maxwell

    In my opinion if anyone is writing such “careless” assignment he has to understand whether or not it’s costly, or not, or maybe even required, no matter the cost. Let’s assume you see something like “Speed previous = result;” and “float previous = result;”. Of course, knowing that the cost of copying a whole struct can be expensive, and a single float can be easily put into helper register, you assume that the first example is costly. That can be misleading as if Speed is defined as “class Speed : public Wrapper {…}” (for strong typing) and compiler will have slightly more work to optimize it.

    You said that “code itself is opaque without the benefit of additional information”. For me the code becomes _more_ readable as you focus only on the structure and algorithms of the code: here you iterate over all elements calling a method “foo()” on them and lastly you make a copy of a result obtained from “create()” method of some factory.

    I don’t know what is your workflow but most of the time I have to quickly consume some block of code and decide to which class should I go and seek further. When a decent part of code is either “using x = “, “typedef …” or “my::very::nested::namespace::SomeFancy ret = x.create();” it’s truly harder to read, as you have to decode it, as – for me at least – that’s like forcing you always to do the next step (knowing the exact types).

    That said I am strongly against putting auto in method/function declarations. Also when in some local scope you would like to cast from right hand or clearly show that you are changing “the mindset”/domain then feel free to use the explicit type. When the type of the left side shouldn’t be shocking to most of the people reading your code, then why make your life harder?

  13. @Richard:

    You’ve sort of touched on this, but when I asked what the performance implications were of

    auto previous = result;
    

    my point was that you don’t know whether you’re dealing with an intrinsic type or an object, whether you’re working with a reference or a pointer or doing a full fat copy (or indeed a type with a custom assignment operator which may do something unexpected). The code itself is opaque without the benefit of additional information. Auto is here actively making it harder to reason about the code.

  14. @Peter The performance cost of

        auto x = make_a_thing();
    

    would normally be low because of return value optimisation. In any case the cost is the same as

        thing x = make_a_thing();
    

    Since we strive to produce code that is user-friendly, the author of ‘thing’ and ‘make_a_thing’ will seek to ensure that the object constructor and the function are efficient and that the function is a candidate for RVO.

    Unnecessary (and possibly dangerous) copies will be generated with this code:

        auto x = get_a_reference_to_a_thing();
    

    which is of course cured with

        auto& x = get_a_reference_to_a_thing();
    

    deleting the copy constructor and copy operator of ‘thing’ catches that at compile time.

  15. I fear I messed up my input and lost the code I was trying to include in the text paragraphs – the text should read:
    What are the performance implications of auto previous = result?
    and
    Certainly, all these questions can be cleared up, if you have an IDE instead of a text editor, by hovering over auto result.

  16. I’ve been using c++11 in production code for 18 months now. I find myself using auto when assigning return values from functions almost always – it seems to make it easier to code for intent rather than mechanics.

    I do still find myself avoiding auto when declaring object variables, since many of my objects are deliberately non-copyable (often because they contain a mutex).

    so I generally do things like this:

        complex_thing x { ... };    // because I don't have to care whether there is a copy constructor
        
        auto thing_ptr = make_shared<thing>(...);
    
        auto result = do_or_make_something(...);
    

    but…

        weak_ptr<thing> weak_thing { thing_ptr };
    

    …because there is no such thing as…

        auto weak_thing = make_weak_ptr(thing_ptr);
    

    … which is a shame.

    in for loops:

        for(const auto& entry : some_container) { ... }
    

    but

        auto container_size = some_container.size();
        for(size_t i = 0 ; i < container_size ; ++i) {
            // although I'm not completely happy with the mix here
        }
    

    perhaps this might be reasonable in principle:

        for(decltype(container_size) i = 0 ; ... ; ... )
    

    but it looks horrid

  17. @peter have to admit did like the idea of auto, one of my pet hates is the horrible typing you have to do sometimes. Maybe the real answer is an IDE function to pickup on what auto really is referring to and substitute the full text definition once the right side is known. Better still the code would be backwards with earlier compilers!

    Or perhaps auto could be reserved for when it’s really useful, but that requires self restraint by the programer, not a known characteristic of the species!

  18. I feel wide use of auto is a mistake, and likely to lead to a maintainability nightmare in the future.

    Firstly, before I produce any examples of my own, I note the previous article’s error in its original state:

    for(auto i = 0; i < v.size(); i += 2) 
    

    With an explicit int, the error becomes more immediately visible. With auto, having to think about the type is elided, dangerously. When a programmer of your calibre makes mistakes in the most trivial of sample code – an indexed for loop – while trying to show that it leads to safer practices, it is hard to resist the conclusion that the opposite may be the case in some circumstances.

    Secondly, you’re far from tackling the real problems with readability. Maintenance programmers look at huge volumes of code. Understanding how safe it is, or isn’t, needs to be as instant as possible.

    auto result = my_opaquely_named_function();
    auto previous = result;
    process(result);
    

    What are the performance implications of ? Will it throw?
    What conclusions can we draw about the lifetime of result?
    If one of those lines does throw an exception, will we have a memory leak?

    Certainly, all these questions can be cleared up, if you have an IDE instead of a text editor, by hovering over . But when you have a page full of autos, having to repeatedly do this to understand what is going on is a pain, a retrogressive step.

    C++ is a big language, and people use it in very different ways. But if there’s a common thread to most development, it is that it takes place in contexts where people will have to read the code again for a long time to come. Const is valuable because the human reading the code can see the const and can rely on the const variable following a certain contract. Auto is the opposite, hiding details of variable contracts in small print.

    And finally, it’s fine to say “it reflects a bias to code against implementations, not interfaces” – but implementations matter. Your interface to void * and a shared_ptr are identical in the example I’ve just given – you can initialize both, copy both, pass both as a parameter – but understanding enough of the implementation details to know what kind of contract they provide you is critical to writing code that is safe and performs well.

  19. @sftrabbit: I’ll only push back on the word “incorrect” which is not compatible with “I’ll admit that this is entirely a matter of style”… Just sayin’. :)

    I didn’t say it’s pitfall-free by default; any feature in any reasonable language can be abused. But it’s objectively true that declaring variables using auto prevents classes of errors (e.g., you can’t write an uninitialized variable), for example. Finally, opinions vary of course — as I’ve pointed out many times, whether/when to use “auto” or “var” or “let” is a perennial question also in other languages that offer such type deduction for local variables with debates going on for years with no sign of abating, so I don’t expect this discussion to end anytime soon.

  20. It’s a choice between using `auto` now and later constraining your declarations or using concrete types now and later relaxing your constraints. It is therefore a matter of weighing up the pros and cons of being overly relaxed against being overly strict, as long as we all agree to use concepts appropriately later.

    I find that a number of your arguments for AAA are either incorrect (particularly about it being readable) or involve shoehorning `auto` into something that it’s not (such as treating it like a var keyword or using it to enforce initialisation). Saying it’s correct-by-default and pitfall-free-by-default is a stretch – only yesterday did a questioner on SO fall into such a pit (http://stackoverflow.com/q/24702953/150634). On the point of readability, AAA requires that type information is moved into the name of a variable to ensure that the meaning of the variable is still clear, which I find to be an ugly practice – the compiler is unable to enforce this, so we have to trust the programmer’s naming abilities. Being overly specific does require more effort on behalf the programmer, but it’s certainly safer and easier to read.

    I’ll admit that this is entirely a matter of style, but I feel like AAA has far too many cons to be suggesting it as the default approach for all C++ programmers (especially beginners). After all, the whole point of introducing concepts is to remove many of these problems from templates. Why would we introduce them to all of our variables?

  21. Yes, ‘auto’ is exactly equivalent to ‘typename T’ by definition (modulo the one quirk that currently auto is allowed to deduce initializer_list).

    As I wrote on the Reddit thread: Second, a huge amount of the discussion in this thread is of the form “but if we had concepts we would write “Conceptname x = …;”. That’s exactly true and I’m one of the ones pushing strongest for that — and it’s a reason to use “auto x = …;” today because it will mean the same thing, namely to deduce the type (not specify it exactly, no conversions, etc.), just adding the ability to check that the deduced type matches the concept. This is a direct (and intended) evolution of declaring with auto per AAA. If you like that, you’re in good company, and you should use auto today. Telling people to declare with concrete types today is not at all the same thing and puts you on a different path of committing to concrete types, which is the opposite of “Conceptname x = …;”.

  22. I’ve written an article (http://josephmansfield.uk/articles/dont-use-auto-unless-you-mean-it.html), which proposes an alternative convention for using auto. It makes some similar arguments, but has a very different result – I suggest using auto only when you want to impose no requirements on a variable. The intention is to unify variable declarations with template arguments, and how we will declare template arguments when concepts are available. I consider auto to be equivalent to writing typename T without any useful information about the requirements of the type. This is okay, but only in situations where you mean that the variable could be any type and behave correctly (such as the argument of std::move).

  23. It’s an X. When used to declare a variable, auto does not deduce to a reference. You need to explicitly include the &. If you write it as auto &, then you get X &. If myDeque were a const deque, then auto & would result in X const &.

  24. Hi Herb, thanks for the informative and well presented site.

    C++11 question du jour:
    given:

    struct X { ... };
    auto myDeque = std::deque<X> {};
    myDequeue.emplace_back( X{} };
    auto x = myDequeue.front()
    

    question 1:
    What type is x?
    a) an X ?
    b) an X& ?
    c) a const X& ?

    question 2:
    Where is the rule book on this?

  25. @Norbert What I’d really like to see with auto return type detection would be the ability to return a local struct. This would nicely encapsulate “multiple return types”, where you currently have to pass things in by reference or return a tuple and use tie, which is icky.

    
    #include <tuple>
    
    bool func1(int input, int& output)
    {
    	output = input;
    
    	return true;
    }
    
    std::tuple<bool, int> func2(int input)
    {
    	return std::make_tuple(true, input);
    }
    
    
    auto func3(int input) // Not actual C++
    {
    	struct ret { bool success; int output; };
    
    	return ret{true, input};
    }
    
    int main()
    {
    	{
    		int output;
    
    		if (func1(5, output))
    		{
    			// ...
    		}
    	}
    
    	{
    		bool success;
    		int output;
    
    		std::tie(success, output) = func2(5);
    
    		if (success)
    		{
    			// ...
    		}
    	}
    
    	{
    		auto data = func3(5);
    
    		if (data.success) // :)
    		{
    			// ...
    		}
    	}
    }
    
    

    Perhaps the local struct could even have a bool conversion operator for success or inherit one from another class. IIRC there are currently limits on local classes (no template members) so this might open up a whole other can of worms.

  26. We have been trying to define a good strategy for the use of auto for a customer’s C++11 guidelines. Researching we found these nice three GotWs about auto. :) Thanks for those!

    Trying to consequently use auto for local variables we ran into three situations where it does not work nicely:

    #include <vector>
    
    
    class foo_t
    {
    public:
       foo_t() {}
    
       foo_t( const foo_t& ) = delete;
    
       void bar() const {}
    };
    
    
    int main()
    {
       // example 1: doesn't work for pointer types
       // auto i_ptr = int*{};
    
       // like this it's fine
       typedef int* int_ptr_t;
       auto i_ptr_2 = int_ptr_t{};
    
    
       // example 2: doesn't work for non-copyable classes
       // compiles with VS2013, but I think it shouldn't (g++ doesn't like it)
       //auto foo = foo_t{};
    
    
       // example 3: auto does worse than manually written code can
       std::vector< foo_t* > foo_ptrs;
    
       // foo is not as const as it could be (only foo_t* const, could be const foo_t* const)
       for( const auto foo : foo_ptrs )
       {
          foo->bar();
       }
    
       // better because it's "more" const
       for( const foo_t* const foo: foo_ptrs )
       {
          foo->bar();
       }
    }
    

    – The pointer example is an edge case. Probably don’t need this very much in C++-code (and there’s a work around).
    – Having non-copyable objects is more relevant though. I regularly see that…probably thanks to Scott Meyers (Effective C++, Item 6: Explicitly disallow the use of compiler-generated functions you do not want).
    – The third situations occurs quite often in legacy code and it just runs against Effective C++, Item 3: Use const whenever possible

    Any thoughts/comments about these three cases?

    Best regards
    Wolfgang Petroschka

  27. Does “AAA” come with a 12 step program? I guess using ‘auto’ is already means trusting a higher power… ;)

  28. People already mentioned searchability, but that is only part of tool support. There are others. For example, ctags is a popular tool used with editors like Vim: I bet it does not support the new syntax well. Even compilers like VC 2012, which I currently use, do not support all the needed language features.

  29. @Ralph Tandetzky: You can also say “for (auto i = 0u; i < v.size(); ++i) {}". Notice the "u" after the "0"!

  30. Some feedback from the trenches: I love auto. I use it all the time. It’s convenient. It simplifies refactoring. It’s useful in templated and non-templated code as well. Im using the auto x = init; type of initialization all the time. However, I tried the auto x = type{init}; syntax for a while and find it slightly less convenient compared to type x = init . I find it more pragmatic to use this in order to say “I wanna commit to a specific type” while possibly getting a conversion operation there. It avoids the restriction to use movable types only (which can be quite a challenge outside the standard library). Whenever I do not need to commit to a specific type I **prefer** to use auto . I’m not a great fan of saying “always” here.

    Most of the time I use auto , I’m actually using const auto which gives me a bunch of single static assignments in my code. This is nice, because I can use a const variable as an equivalent to the expression on the right hand side and it makes the code very easy to reason about. At the same time I don’t need to care about the type of the right hand side. These two keywords are an awesome combination!

    One more thing: I found it quite annoying to get compiler warnings when I write stuff like this:

     for ( auto i = 0; i < v.size(); ++i ) {...} 

    It warns me that I get a comparison between a signed and an unsigned type. This is one of the places where I prefer to write size_t instead of auto .

    Thanks for the many insightful posts. Keep it up!

  31. On “auto x = init; […] It […] guarantees that no implicit conversions […] will occur.” Well, array-to-pointer and function-to-pointer conversions might occur. These conversions are, surely, efficient (loosely speaking they are “compile-time conversions”) and, I believe, no other conversion might occur.

    
      int a[3] = {};
      a = nullptr; // invalid: type of a is int[3] 
      
      auto b = a;
      b = nullptr; // OK     : type of b is int*
      
      auto& c = a;
      c = nullptr; // invalid: type of c is int(&)[3]
    
      // ...
      
      void f();
      f = nullptr; // invalid: type of f is void()
      
      auto g = f;
      g = nullptr; // OK     : type of g is void(*)()
      
      auto& h = f;
      h = nullptr; // invalid: type of h is void(&)() 
    
    

    I think it’s also worth mentionning that qualifications are not preserved (especially because they are often mistaken as conversions):

    
      int const i = 0; // or "auto const i = 0;" :-)
      i = 1;       // invalid: i is const
      
      auto j = i;
      j = 1;       // OK     : j is not const
      
      auto const k = i;
      k = 1:       // invalid: k is const
    
    
  32. I’m curious, how it is supposed to use auto for classes w/o copy/move constructors? For example if it contains std::atomic type and the only constructor with some parameters.

  33. @ned I believe the non-searchability isn’t that bad if you consider that at least the interfaces of the classes/functions will have to explicitly state the type. Because of that I am not a big fan of the auto-deduced type of non-template functions. Also if you use the “auto f = Foo{ sth. }” you will still be able to find the usages of your class in your codebase.

  34. @J Guy Davidson: Actually, I also noticed that the example isn’t really relevant because it has nothing to do with auto — taking the address of an overloaded function and storing it directly in function is problematic with or without the auto style syntax, so I’ve removed the example.

  35. I doubt the ‘auto x = type{ init };’ style, in place of the standard ‘type x{init};’, is going to catch on. This is because, as noted, it cannot be used with types which are neither copyable nor movable, so it cannot be used universally. I discount the fact that (to me) it looks plain odd, because that can pass with usage and familiarity.

    The standard C++ library tries hard to make as many aggregate types as are reasonable movable, but in user code I commonly come across, and write, there are often types which are neither copyable nor movable. There are a number of reasons for this: two types may be closely coupled as part of a single implementation, so objects of those types may hold references to each other. Trying to track which object happens to own some movable resource in such cases is often far more trouble (and introduces more inefficiency) than it is worth. Efficient movability also implies constructing the internal implementation data of aggregates on free store so the data can easily be moved by swapping pointers. However where efficiency is at a premium for objects likely to be constructed on the stack and unlikely to be moved, constructing things like arrays with known sizes at compile time may be desirable, and member arrays of aggregates (or std::array objects) can only be copied in linear time and not moved. In those cases you may want to prohibit copying/moving entirely. Users who in a particular case want to move and are willing to take the hit of allocation can call make_unique() for the unmovable type in user code where they really want to.

    Lastly of course, prohibiting moving and copying may be necessary to enforce the thread-safe use of some objects at reasonable cost. And some objects cannot semantically properly support moving (the article mentions locks, but there are many others).

  36. Excellent arguments. However, unless I don’t understand std::function or these chaps http://en.cppreference.com/w/cpp/utility/functional/function have got it wrong, there’s a misspelling in the last line of your third code block in section 6.

    auto f = function<int()>{ &func };   // ok

    should be

    auto f = function<void(int)>{ &func };   // ok
  37. With all the issues about the readability of the type of an “autoed” variable, we must see that what is more important is not the variable itself, but instead its initializer. Consider this somehow innocuous piece of code

    auto result = make_something();
    result.do_something();
    send_something(result);
    

    Long story short, by using auto, we are emphasizing the initializer, as the initializer is the one who gives the actual type of its result. By emphasizing the initializer, we are emphasizing the operation(s) involved in the initializer. In the above code, make_something() is actually the one who is more important. It is a clear statement that “we are making something, then return some result”. But this is just for the functional programming part…

    Now you may (and you will) argue “Hey! We are now in an OOP world! result there would almost always be an instance of some class. make_something() wouldn’t matter if doesn’t clearly know what we can do with result.”

    “what we can do”
    “can”

    What should you do? “Where do you want to go today?” not “Where can I go today?”. “What operations should I do?” not “What operations can I do?”. A good thing with OOP is that we are encapsulating data with operations. The operations that we do is what matters. But what matters most is what operations we _should_ do. (You may see that it’s analogous to test-driven development.)

    With the line result.do_something();, we are not focusing on what result is (i.e. what’s its exact type), but instead, we focus more on the operation. Sometimes we think it the wrong way with OOP. With its syntax we often we read that line as “result, do_something()” (emphasis on the object) where instead it should be “do_something() with result” (emphasis on the operation). Now you are seeing some reasoning template library authors usually have. These are the properties of templates, of generic code. And now, we can enjoy it in non-template, “daily” code.

    My own sentiment with auto is a very positive one. As said, it brings much generic code goodies without writing inside a template at all. Most importantly, it makes your code emphasize more on the operations, something we’ve been taking for granted for the past few years.

  38. I didn’t quite follow why “auto e = employee{empid};” is better than “employee e{empid};”. It’s more typing which is a (tiny) negative. Is it just for consistency with other declarations?

  39. WRT “void” being a type, I’ve always wished that C++ would define “void” as the base class for all types. This would permit all of the following:


    void * p = new string("hello"); //already valid
    const void & r = string("hello");

    void func(const void &);
    func(3);
    func(string("Hello"));

    etc…

    -J

  40. @avjewe: I’m taking my own advice and trying it out. I still forget sometimes, but so far the more I use it the more I like it.

    @mttpd: Thanks, open/closed noted. Re decltype(auto): That’s for something else. :) That’s deducing a decltype expression, but it’s still decltype, whose uses are different from auto.

    @ned: Searchability is a valid objection too. Noted, thanks.

    @Nemanja: If it helps, to clarify I’ve added this: “Granted, this is generic code, and not all your code will be templates—but the point is that the code isn’t unreadable even though it doesn’t mention specific types.”

    @Jeffrey: If the expression templates return proxies that refer to UDL-generated temporary objects, that sounds like a pitfall inherent in the expression template library that will trip people up in lots of cases, not just with auto. This reminds me of a sort-of related tiny example that illustrates this class of design problem (and it is a design problem, not an auto problem): Andrei Alexandrescu complains about std::min, which takes and returns a reference, so even simple auto-less code like “min(x,f()) = g();” can be problematic. (I think I got that example right.) This seems to be a library design problem regardless of auto, not a new problem or pitfall introduced by using auto.

    @zadecn: Thanks. The vector<bool> part of the example was actually not a difference with auto — auto and bool do the same thing so there’s no auto-related issue here. I’ve removed the example.

    @noniussenior: void? Sigh. C’mon, void isn’t a meangingful type. But okay, because technically it is, I added weasel-wording. Fixed.

    @Petter: Just vector. And this is a known pitfall that bits in all sorts of cases; it’s not new with auto.

    @gnzlbg: as_signed and as_unsigned were “shown in GotW #93.”

    @Marcel: I thought I mentioned initializer_list but must have accidentally cut that (I did mention it in GotW #92). Re-added, thanks.

  41. Think some of the comments given here are perhaps the most valid reason for not using auto, and that is lack of transparency resulting in the unexpected. Never a good thing!

    Using auto where the good old way would work seems pointless and doesn’t help readability or sanity checking. For example some legacy code returns a float you then auto the returned value and do a lot of maths work with it using auto, silently taking the penalty for using a float rather than a double.

    Auto works for me when the type is complicated to work out or where I really should not care, which is of course what it’s meant for.

  42. I really like one more in C++14 upcoming application of auto:
    Consider:

    class C {
    private:
        // real internal things:
        // C++11:
        class Internal;
        Internal doSomething();
        
        // C++14:
        //auto doSomething();
    };
    

    In C++11 and before I have to name the return type of “doSomething()”. Although that can be done by forward declaring a local class, it feels even nicer not to have to mention that class in C++14. (At least that is how I understand N3638)

  43. I personally prefer to use auto to declare local variables. But unfortunately there are some problems with auto and uniform initialization. Perhaps it is a good idea to shed some more light on these pitfalls:
    Always use copy-initialization for variables declared with auto, i.e. auto x = expr; where expr is an expression. The point is, that a braced-init-list is NOT an expression. A corollary of this guidline is: Never write something like

    auto x = {1};
    

    The type of x will most likely not what you expect: Here x will have type std::initializer_list!

    For another example consider the following code:

    class X
    {
    public:
        X() : x_{0} {}
    private:
        int x_;
    };
    
    double f() { return 4.2; }
    double d{f()};
    

    We teach people to use uniform initialization (in this case direct-list-initialization) for class members like: x_{0}; or variables like: double d{f()}; This avoids the “most vexing parse”:

    double d(f());
    

    But if we write

    auto d{4.2};
    {/code]
    then we get a variable d of type std::initializer_list, which again is most likely not what we want. There is a proposal (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3681.html) which will solve many problems mentioned above.

  44. Because of what Jeffrey Bosboom said, I think the my_vector_bool is a really bad example, since the following two code does something really different (and because the vector specialization already considered as a mistake).

    auto b1 = my_vector_bool.back(); // to capture the proxy
    b1 = true; // write into my_vector_bool
    
    auto i1 = my_vector_int.back(); // No proxy
    i1 = true; // NOT write into my_vector_int
    

    [[HS: As noted above, this example was incorrect, auto and bool do exactly the same thing so there is no issue. Example removed.]]

  45. I must say that the following is very error-prone:

     
    auto b1 = my_vector_bool.back();
    // ...
    b1 = true;  // Oops! my_vector_bool changed!
     

    compared to

     
    bool b1 = my_vector_bool.back();
    // ...
    b1 = true;  // vector not changed.
     

    In almost all cases, auto will give you a copy. vector is error-prone combined with auto.

    [[HS: Note I was wrong about this example, writing ‘auto’ and ‘bool’ have exactly the same semantics here and so auto is not error-prone with vector<bool>. I’ve noted this below, and removed the (non-)example. Sorry for the distraction.]]

  46. “Quick quiz: How many specific types are mentioned in that function? Name as many as you can.

    void some_function( Container& c, const Value& v )
    …”

    Why is void not a specific type?

  47. The comments given below have a little bug:
    auto b1 = my_vector_bool.back(); // to capture the proxy
    b1 = true; // write into my_vector

    I think the correct is :
    auto b1 = my_vector_bool.back(); // to capture the proxy
    b1 = true; // write into my_vector_bool

  48. the codes and comments below have a little bug:

    auto b1 = my_vector_bool.back(); // to capture the proxy
    b1 = true; // write into my_vector

    I think the right is :
    auto b1 = my_vector_bool.back(); // to capture the proxy
    b1 = true; // write into my_vector_bool

  49. Two points about auto and expression templates:

    –If you use ‘auto a = x * 5someliteral’ where * returns an expression template, that template has a reference to a temporary object that will be destroyed at the end of the expression. So you can use auto to hold an expression template object, but you can’t safely do much with it (in particular, evaluate it). This feels like a trap, especially when combined with the next point.

    –If you change an operator that used to return a Foo to return an expression template convertible to a Foo, any code that used to use auto can break as above because it gets an expression template. Despite using auto, if you want the flexibility to introduce expression templates after-the-fact (say, after profiling shows you need them), you need to use the ‘auto a = Foo{expr}’ form everywhere. And because there’s no types, it’ll be hard to find the locations whose meaning changed, unless you have an IDE that can find all usages of e.g., operator*. You might not even know about breakage if it’s in some template (and similarly the template author has to decide between ‘auto a =x*y’ and ‘auto a = T{x*y}’).

    I recall there being some consideration of an ‘operator auto()’ to instruct the compiler what type to infer, and I’m not yet convinced it isn’t necessary.

  50. There is one major problem with your reasoning, and it can be nicely summed up in this sentence:

    “Zilch. Not a single specific type appears anywhere in this code, and the lack of exact types makes it much more powerful and doesn’t significantly harm its readability.”

    The code sample you gave is very non-typical. That is generic, template-based code usually written by library implementers. I write code like that in my open-source project, but never at work. Code I write at work is full of types and except in a few corner cases such as declarations of iterators explicit types do improve reliability and robustness of code.

    In short, I think I agree with your advice to use “auto” as much as possible, but only for template libraries. For all other code, “auto” should be used only in very few cases.

  51. Another downside of using auto is search-ability – it makes it very hard to search a code base to find all the places that an object is used. This can be an issue re-factoring an interface – they don’t always get written perfectly the 1st time. In a smaller project, a good IDE can help here, but when a code base spreads across 10’s or 100’s of projects its hard to beat find-in-files.

  52. A few extra thoughts and a question ;-)

    * “Compile-time duck typing” is generally referred to as “structural typing” (or “property-based typing”):
    https://en.wikipedia.org/wiki/Structural_type_system

    * In the “What exact type is Container?” paragraph, perhaps it’s also worth mentioning on how it helps with satisfying the open/closed principle?

    * I would still advise against using auto for the loops’ counting/indexing variables:
    – I happen to very much care about expressing the non-negativity intent via the type system (so sloppy and wrong “auto i = 0” is absolutely out),
    – I also care about automatically matching the right data model (register size) without having to worry about such low-level details as whether I’m compiling for the LLP64 data model or the LP64 data model or anything else (so contortions such as platform-dependent macros choosing between the suffix UL and the suffix ULL are most definitely out),
    – I also want zero-overhead portability (this is C++, after all), so committing to ULL (other than not being future-proof and generally violating the previous constraint), is out as well,
    – “for (auto i = std::size_t{0};”, …, really? :-)

    This could have been solved if we only had the suffix for std::size_t (ZU anyone? ;]), but as of today I don’t really see any alternative.

    * How does decltype(auto) fit into the big picture? :-)

  53. What if you want to use the default constructor?
    Would you still advocate
    auto x = type{}
    rather than
    type x;
    ?

Comments are closed.