With Respect to « Proper Usage » of auto

Some shortcuts:

A French version of what follows is available here. I normally don't write bilingual versions of the articles you'll find on my site, but what follows was written following discussions wiht English-speaking friends and I wanted to make sure they could read it.

What follows expresses my thoughts (for the time being) with respect to the auto keyword as it is used to define a variable. These thoughts are incomplete and still evolving, but I hope they will contribute something to the debate.

Please note that the auto keyword is used for other reasons than those discussed below, including the types returned by functions and the type of their arguments. I don't have the time I would need to cover these here for the time being.

The auto keyword was traditionally reserved for what we could simplify as « a local variable » (automatic allocation, on the stack, as opposed to static) and was traditionally not used since is was essentially redundant. Since C++ 11, this keyword makes it possible to express, when defining a variable, « the variable's type is the same as the type of the expression used to initialize it » (minus références and const and volatile qualifications).

For example:

static const int glob = 3;
int &f() { return glob; }
int g() { return glob; }
int main() {
   int i0 = glob;       // i0 is int and is initialized with a copy of glob's value
   auto i1 = glob;      // i1 is int (not const) and is initialized with a copy of glob's value
   auto i2 = f();       // i2 is int (not int&!) and is initialized with a copy of glob's value
   auto i3 = 0.5 + g(); // i3 is double since expression 0.5 + g() is double
}

Quite a few experts are debating the merits of using auto to « specify » a variable's type There are at least three groups involved:

The AAA Position

The AAA position recommends that we use auto to define a variable almost as a reflex. This position states (rightfully) that the resulting text is homogeneous and prevents such things as forgetting to initialize a variable.

Without auto With auto Notes
int i = 0;
auto i = 0;
// or
auto i = int{};
// or
auto i = int();
// or
auto i = int{0};

Like most people, I would not use auto here, but a supporter of AAA would remind us that this:

auto i = int();

... compiles and initializes i to zero, whereas this:

int i();

... compiles but could surprise by declaring a function named i that takes no argument and returns an int.

std::vector<std::string> v = { "I love", "my", "teacher" };
for (std::vector<std::string>::const_iterator it = v.cbegin(); it != v.cend(); ++it){
   std::cout << *it << ' ';
}
// or
for (const std::string &s : v) {
   std::cout << s << ' ';
}
std::vector<std::string> v = { "I love", "my", "teacher" };
for (auto &it = v.cbegin(); it != v.cend(); ++it){
   std::cout << *it << ' ';
}
// or
for (const auto &s : v) {
   std::cout << s << ' ';
}

Here, for the first loop, it would feel natural to use auto for it as well as begin(v) and end(v) for the extremities of the sequence we want to iterate on. This would make of it, contextually, a vector<string>::iterator or a vector<string>::const_iterator depending on the type of v.

I used v.cbegin() and v.cend() to make examples similar to one another, nothing more.

tetemplate <class It>
   void f(It begin, It end) {
      typename std::iterator_traits<It>::difference_type dist = std::distance(begin, end);
      // ...
   }
template <class It>
   void f(It begin, It end) {
      auto dist = std::distance(begin, end);
      // ...
   }

This example shows, I think, how useful the auto keyword can be.

We could go for a very explicit type declaration, such as shown in the left example, but it's unclear how it would be advantageous. In such a case, I doubt most programmers would really benefit from explicitly stating the type; should one not be conscious of the fact that the return type of std::distance() should be signed, explicitly stating the time would probably not provide more information.

template <class M, class F, class ... Args>
   auto minuter(M minu, F f, Args && ... args) -> std::pair<decltype(f(std::forward<Args>(args)...)), typename M::duration> {
      typename M::time_point before = minu.now();
      decltype(f(std::forward<Args>(args)...)) result = f(std::forward<Args>(args)...);
      typename M::time_point after = minu.now();
      return std::make_pair(resultat, after - before); 
   };
template <class M, class F, class ... Args>
   auto minuter(M minu, F f, Args && ... args) {
      auto before = minu.now();
      decltype(auto) result = f(std::forward<Args>(args)...);
      auto after = minu.now();
      return std::make_pair(resultat, after - before); 
   };

In this case, using auto frees us from having to know the names of types exposed the the language's standard clocks, in such a way that we can express the same algorithm without loss. It also frees us from repeating ourselves, thus reducing the risk of error.

There is a subtelty associated with the type of result in the right-hand example. Here, simply using auto would have probably worked, in most cases, but would have induced a form of semantic drift... A sign, I think, that one should not stop thinking, even when using something such as auto.

Using auto to define a variable turns uninitialized variables into illegal expressions. Indeed, this would not compile:

auto x;

... whereas this would compile but leave x in an uninitialized state:

int x; // legal, but...

Aesthetically speaking, an upside of using auto is that it aligns variable names as they are being defined in a given block, all « type names » being of the same length.

The AAAA Position

The AAAA position is that using auto when defining a variable distances source code from its semantics, which can lead to errors and complicate maintenance.

With auto Possible intention In practice
auto s = "I love my teacher";

The programmer might have wanted to make s of type std::string.

Here, s is a const char*. We could have made it a std::string by using a more explicit right-hand side, such as:

auto s = string{"I love my teacher"};

... or, since C++ 14, with a user-defined literal:

auto s = "I love my teacher"s;
auto x = d + sizeof(T);

The programmer might have wanted to compute the size required in order to store a T and something that is d bytes-wide.

The type of x is a mystery: is it signed or not? Integral or not? Could the value of d be negative? This example comes from James McNellis, who claims (rightfully) that using auto here is a really good way to get oneself into trouble.

std::vector<std::string> v = { "I love", "my", "teacher" };
for (auto s : v) {
   std::cout << s << ' ';
}

The programmer will iterate over each string in v but, in so doing, will create a potentially costly copy of each element.

Writing either for(auto &s:v) or for(const auto &s:v) would solve this one.

With a loop that makes it explicit that each s is a string, explicitly writing string& or const string&, would have also done the job.

One could plead that auto, which might seem magic in the eyes of some, obscures slightly this mostly hygienic issue.

template <class M, class F, class ... Args>
   auto minuter(M minu, F f, Args && ... args) {
      auto avant = minu.now();
      auto result = f(std::forward<Args>(args)...);
      auto apres = minu.now();
      return std::make_pair(resultat, apres - avant); 
   };

The value returned by f(args...) will be copied in result, but this could break the expected semantics should this function call return a reference.

As stated above, decltype(auto) is the à la auto solution with C++ 14 here, and explicitly stating the return type with a complex decltype(...) expression would also work.

Clearly, using auto should not lead us to stop thinking when we are coding.

The CRA Position

What follows supposes that one writes short functions with a clear focus; if that's not part of your programming practice, whatever the reason, then the AAAA position is probably better suited for you.

Essentially, why do we use auto when we do so?

On the teaching front, the virtues of auto are many: simpler indentation when defining variables, simple directives that work well in general for beginners, reduced risk of uninitialized variables, etc.

In practice, auto is not magical, and using it without thinking can lead to unpleasant side-effets, in particular when manipulating integers (issues of size and signedness). Of course, coding without thinking is a questionable practice, with or without auto.

Using auto is a good idea, in my mind, when:

In a situation such as the following:

template <class It>
   void f(It begin, It end) {
      auto dist = std::distance(begin, end);
      // ...
   }

... I'm not convinced that writing auto instead of typename std::iterator_traits<It>::difference_type has a detrimental impact on readability or on readability. Indeed, I think that:

Likewise, take this function:

template <class C>
   void afficher_elements(const C &c, std::ostream &os) {
      for (auto &val : c)
         os << val << ' ';
   }

In this case, supposing that c is a container, it's possible to express the exact type of each element explicitly:

template <class C>
   void afficher_elements(const C &c, std::ostream &os) {
      for (typename C::const_iterator it = c.begin(); it != c.end(); ++it)
         os << *it << ' ';
   }

That being said, what would we gain from this explicit typing? Is it clearer? Easier to maintain?

Then, of course, abusive usage of auto does exist. The poster child probably is:

auto n = 0;

... where using auto takes more characters than using int explicitly, and runs the risk of confounding those who will read the code a posteriori. Likewise, this:

template <class T, class F>
   void g(T val, F f) {
      auto x = f(val);
      // ... lots of code
   }

... can be tricky. What's the intent behind the application of f to val? What does g() expect of x in practice? Here, constraining the type of x would at the same time be a form of in-code documentation and a guide to its ulterior maintenance.

My Personal Usage of auto

I personally write auto when:

I avoid auto when:

Whenever it's reasonable to express type constraints, we have many options (not necessarily in order of preference, such an order being somewhat cultural):

What about the AAA approach, which recommends almost always using auto? In some projects, maybe, and it seems reasonable for teaching some introductory programming classes.

What about the AAAA approach, which recommends avoiding auto except in select cases? I would not go as far, the cases of meaningful auto seem too numerous to me to impose such a restriction.

Is the type clear from context? Then, auto is probably appropriate.

Is the exact type irrelevant in the context of the algorithm? Then, auto is probably appropriate.

Some Thoughts...

A direct consequence of auto usage is, it seems to me, a greater emphasis on explicitly type literals, inclusing user-defined literals. I'm not sure what to think of this yet, but it is a twist given existing practice, where we had a tendency to let constructors perform conversions for us given literals of various types. This was something one could observe already through usage of such functions as std::accumulate(), as the return type of that function is determined by the type of one of its arguments.

Acceptance of auto is in large part driven by our habits in generic programming. We have come to accept using very abstract types in our code, which are only determined at point of usage, typically far from the location where the templates are actually expressed. The tools we have built (traits in particular) to constrain types and precise our intentions remain valid, but are sometimes a burden. I do not think auto replaces them completely, even though it plays a rule in alleviating some of their tediousness.

I expect concepts, as they become available, to replace auto usage in some cases. They are not as tedious to write as things such as traits on dependent types, and constrain generic code just enough to make the intent clear and code more robust.

In a 2015 presentation on migrating code from C++ 03 to C++ 14, Joel Falcou examines auto from a compile-time-shortening perspective, and offers the following guidelines :

He adds that decltype should be used for metaprogramming tasks and SFINAE context in auto return types. These, in his experience, bring the best compile-time performance results, as long as one remains conscious of the caveats of auto deduction rules, discussed at length in Scott Meyers' Effective Modern C++ book.


Valid XHTML 1.0 Transitional

CSS Valide !