auto unless you mean it
Since it's repurposing in C++11, the
auto keyword has largely been used as a convenience. It's one of the more well-known new features, perhaps because many programmers saw it as a way to simplify their code, but also has little consensus on when it should be used. It provides automatic type deduction in declarations based on their initializers. For example,
auto x = 5; will deduce that
x is an
To beginner C++ programmers,
auto is seen as a way to increase the brevity of code and hide away ugly C-style declarations, with the intention of making it easier to read and faster to write. After all,
auto can save us from typing out the hideously long types we often find in C++. Some suggest to use
auto as much as possible. I strongly feel that hiding away types is a stylistic choice and therefore the responsibility of the IDE and not of type deduction. Also, this is a difficult convention to stick to because it's hard to agree on whether a particular use is acceptable or not. Consider the following:
Is it clear what we can do with
best_candidate? Is it the maximum value or an iterator pointing at the maximum value? If it's an iterator, what kind of iterator? For somebody who is fairly comfortable with C++, perhaps this might be obvious. For anybody else, we'd have to look up the definition of
std::max_element just to find out what its return type is and then figure out what that type represents. This convention is therefore a matter of deciding how proficient a person needs to be to understand your code. If the initializer used functions and types from some other library, the reader has to be proficient with those also. When using
auto, you are arbitrarily making the choice of whether it is acceptable or not. Additionally, if the initializer changes to give a type that is no longer suitable, you'll get errors from further down the code. This doesn't sound great to me.
Instead of using
auto so much, my proposed convention is as follows: choose types that impose the minimum requirements on a declaration that are necessary for the code in which it is used to behave correctly. Under this convention,
auto imposes no requirements. It says “This variable can have any type and the code in which it is used will run just fine regardless.” This gives
auto some meaningful semantics. You may correctly observe that this means we have only two choices: no requirements with
auto, or no leniency with the actual type itself. That is, for now.
A proposal to introduce concepts to C++ is in the works (N4040 is the latest draft at the time of writing). Concepts would allow us to place requirements (called constraints) on template arguments. At the moment, a template argument (like
typename T) will accept any type, which results in horrible errors being thrown from the internals of the template if that type is not appropriate. Go ahead; try doing something like
std::copy(5, 10, “foo”) and enjoy the mess. Worse, it could compile fine and behave erratically. With concepts we'll be able to specify requirements on the arguments:
In fact, a terse syntax is also proposed which will allow us to use concept names in the argument declarations themselves:
So with concepts, saying that a parameter is an
InputIterator says that the arguments should meet the requirements imposed by the
InputIterator concept. If you pass something that doesn't, you'll get a nice clean error message.
We can also use
auto in this terse syntax as the equivalent of
typename T with no constraints:
Given the semantics of concepts for arguments, this means “I impose no requirements on the type of
something.” Anything will do. This syntax is also being introduced for generic lambdas in C++14:
It's intended that concepts will eventually be usable in normal variable declarations too, although a proposal has not yet been written. The previous
std::max_element example could then be written:
This implies that any use of
best_candidate only requires that it meets the requirements of an
InputIterator. We are hiding away the actual type of the iterator, but not hiding the information that is actually important. On the other hand,
auto hides everything. You can hopefully now see that the convention I have proposed unifies the declarations of both function arguments and variables:
In both cases, you give the type or concept that has the least requirements required in order to use that object in the way you want to. Under this rule,
auto means anything. Likewise,
auto& means an lvalue reference to anything,
auto&& is a universal reference, and so on.
This convention has multiple additional benefits. In the same way that template arguments allow the arguments to a function to change type, using concepts and
auto allows the initializer of a declaration to change. As long as the initializer returns an object that meets the requirements, everything is fine. It also ensures that a nice clean error will be reported if a type is used that doesn't meet the requirements, unlike using
auto which has the same problems as templates do today. It also makes the code easier to read than if
auto were used for everything because you only need to understand what the type or concept represents. Also, concept names are brief and to the point.
If you see
auto under this convention, it means you really, really do not need to care about the variable's type. This means it should be used significantly less than it is being used now. For example, I would not use it in a range-based
for loop unless I could really be iterating over anything. Instead, I'd use the type itself:
In this example, I'm calling
contact_details::get_email_address, which means that
contact has to be a
contact_details object. This has a number of advantages over using
- Finding the type of
contactonly requires looking at its declaration and not at the type of
- If the container type changes, you'll get an error directly from the
forloop declaration, rather than deeper in your code.
- It expresses your intent to use
contactin ways only a
contact_detailsobject could be used.
- It's consistent with all other declarations including parameters of templates.
I'd say it's also better than writing
decltype(v)::value_type because that ties the declaration to the initializer by assuming that it has a
value_type typedef. Really,
contact doesn't need to care.
If I were writing a generic loop that could iterate over any kind of object and process it,
auto would be fine:
Since concepts won't be around until C++17 and the ability to use concepts in normal variable declarations may not appear until later, this convention requires a lot of patience. Just don't get too excited about
auto and use it everywhere. Instead, use it with some idea of how it will be used in the future. For now, use specific types in most cases. It's better to be overly specific than overly general, because using
auto unnecessarily can lead to unclear code and confusing bugs. Using it too much now will make your code less idiomatic in the future. The only downside will be that if you change the type of the initializer, you'll have to change the type of the declaration, but that shouldn't happen too often. Obviously for now we need to be overly general with templates, otherwise they'd be useless, but for declarations we should prefer to be specific.
Herb Sutter has previously proposed AAA Style (Almost Always Auto), which is pretty much the opposite of what I'm suggesting. However, we don't entirely disagree. Many of his arguments actually back up what I'm suggesting, particularly in terms of writing against interfaces instead of implementations. In his
append_unique example, he remarks that
Container& c makes it clear what the requirements of
c are. This is simply mimicking what concepts will provide and is certainly a good practice for template arguments while we don't have them. However,
auto doesn't have this benefit at all. Instead,
auto is like writing
typename T without any information about requirements. It certainly is less readable, perhaps not for the writer of the code, but for anybody else who wants to read and understand it.
One problem that the AAA Style tries to solve, which I do not, is the horrible C declaration grammar. By using
auto everywhere, we can easily see that a line is a variable declaration and we don't have to decode its type. Firstly, I don't think this should be the responsibility of
auto, which is a much more powerful tool than a
var-like keyword. Secondly, we can generally avoid the incredibly ugly type names and concepts will help us to do this.
auto means anything. Only use it when you mean it, otherwise you make your code harder to read and introduce further problems. Just be patient for concepts, which will make this convention unified and logical. It'll pay off.