Jonathan Boccara's blog

“auto to stick” and Changing Your Style

Published September 28, 2018 - 19 Comments

Daily C++

While performing a code review on a refactoring project, I stumbled upon a change that took a line of code from this state:

Widget myWidget{42};

to that:

auto myWidget = Widget{42};

Well, in the actual case the type wasn’t called Widget and the initialization value wasn’t exactly 42. But that’s the gist of it.

What’s the point of adding an auto here? It was tempting to emit the W-word, followed by the T-word and completed by the F-word, but following Arne’s good advice on code reviews, I went instead for a comment saying something like: “Are you sure this is not over-using auto here?”.

The reply surprised me.

My esteemed fellow developer whose code I was reviewing directed me to a CppCon talk of Herb Sutter explaining the cases where it was preferable to use auto. I watched the talk with interest and discovered that the above case was one of them. It is called “auto to stick”.

If, like I was, you are not among the 100,000+ people who had watched that talk (!), or you don’t remember it, let me refresh your memory about “auto to stick”. And then let’s reflect on the topic of changing our coding style.

Thanks to Herb Sutter for reviewing this article!

Stick to “auto to stick”

What does “auto to stick” mean, to begin with?

There are two usages of auto to initialize a value. The first one is called “auto to track” and is used to deduce a type from an initialization expression:

std::vector<Widget> widgets = {1, 2, 3, 4, 5};
auto first = begin(widgets);

The type of first is deduced as being what the begin function returns, that is to say a std::vector<Widget>::iterator here.

The second usage of auto to initialize a value is our “auto to stick” and it is used to commit to a type. For instance:

auto name = std::string{"Arthur"};

This forces name to be of type std::string. Otherwise the following code would have made it of type const char *:

auto name = "Arthur"; // name is of type const char*

So this is “auto to stick”. Now we could have achieved the same result by writing:

std::string name = "Arthur";

Which is what we’ve been used to since kindergarten (ok, maybe a little later) and it looks simpler at first sight.

But Herb Sutter makes a compelling argument why we should move to “auto to stick” for initializing values.

The consistency argument

Essentially, the argument is that the default style of C++ is moving towards a “left to right” syntax.

This means that the more recent versions of the language brought a common pattern for definitions: a name on the left of the equal sign, and the initialization info on the right of the equal sign.

Note how our “auto to stick” is exactly structured that way:

auto myWidget = Widget{42};
     ^^^^^^^^   ^^^^^^^^^^
       name     init info: type + value

And when you’re aware of this pattern, you can see it everywhere. For instance, look at heap allocation:

auto myWidget = new Widget{42};

And with modern heap allocation using smart pointers:

auto myWidget = std::make_unique<Widget>(42);

The name of the object myWidget is on the left of the equal sign, and all the initialization info is on the right.

Lambdas declarations follow that pattern too:

auto myLambda = [&context](type argument){ /*body*/ };

Name on the left, initialization info on the right.

Literal suffixes are no exception to that rule. For example, this C++14 code:

using namespace std::chrono_literals;
auto countDown = 10s;

On line 2 in the above piece of code, the name of the object initialized, countDown, is on the left of the equal sign and its value (10) and type (std::chrono::second) are deduced from the right side of the equal sign.

Going beyond auto, the alias expression with using added in C++11 also follows the left-to-right pattern:

using Dictionary = std::unordered_map<std::string, std::string>;

Finally, going beyond the syntax with an equal sign itself, consider C++11’s function declarations with trailing type. Even though they don’t have an equal sign, they follow the left-to-right pattern:

auto f(std::string) -> int
{
    // ...
}

The initialization argument

One of the strongest arguments for the “auto to stick” syntax is that it makes it impossible for objects to be uninitialized. To illustrate, consider this (incorrect) C++98 definition of an int:

int i;

C++ requires objects to be initialized before they are used, and since i is not initiliazed, reading from it will cause undefined behaviour. Said differently, this code is a crash waiting to happen. But this code compiles.

In C++98, we used to fix it this way:

int i = 0;

By using the “auto to stick” syntax, defining an uninitialized object cannot compile. Indeed, if we start by auto, we have to specify an initialization value. In particular, we cannot write this:

auto i; // doesn't compile, not enough info on the type of i

Nor that:

auto i = int // illegal expression

We have to go all the way and initialize the object:

auto i = int{}; // i is now initialized (to 0) and can be read from

The no narrowing conversion argument

Now consider this syntax to initialize a float:

float x = 42.;

There is a narrowing conversion going on: 42. is of type double, and is converted to the less precise type float.

With the “auto to stick” syntax, no double was ever created in the first place:

auto x = 42.f;

And there is no narrowing conversion happening.

The it-almost-never-affects-performance argument

Consider this expression using “auto to stick”:

auto myWidget = Widget{42};

What’s behind this equal sign? Isn’t there a copy of Widget going on?

Theoretically, the expression Widget{42} creates a temporary Widget which is then moved to myWidget. So this syntax could incur the price of a move.

But even then, the compiler is allowed (and compilers are getting very good at that) to elide this move, and direclty construct the Widget{42} inside the memory space of myWidget. So the “auto to stick” syntax would incur no runtime performance impact at all.

The exceptions to this rule are the cases where the move constructor of the particular type is not cheap (for instance, std::array) or not existent (for instance std::lock_guard). Note that this constraint only holds in C++14 and earlier (see edit below).

It’s then interesting to compare this guideline to the one of Scott Meyers in Effective Modern C++ item 29: Assume that move operations are not present, not cheap, and not used. Are the two guidelines in contradiction then?

I don’t think they are, because Scott Meyers talks here about unknown types, like in template code for instance. So to me, it goes along with Herb Sutter’s guideline but implies that we should avoid the “auto to stick” syntax in generic code, in C++14 and earlier.

EDIT: As pointed out by Herb Sutter and Patrice Roy, since C++17 the compier is required to elide the temporary object as well as the move operation. So from C++17 on we can safely use “auto to stick” even for types with expensive (std::array) or inexistent (std::lock_guard) moves, because they won’t be called at all in an “auto to stick” expression.

The most vexing parse argument

C++ most vexing parse consists in your compiler parsing this type of expression:

X x();

as a function call declaration, even if you intended it to default-construct an object of type X that is called x. C++ requires to interpret this as a the declaration of a function called x, that takes no parameter, and returns X (for more details about the most vexing parse, read item 6 of Effective STL).

Even if there are multiple ways to work around it, note that using “auto to stick” allows to avoid the most vexing parse:

auto x = X(); // no way to interpret this as a function declaration

Thanks to Eric Albright for pointing this out.

Changing your style

Now are you ready to change your coding style and define your objects with “auto to stick” syntax?

A style that’s different from our habit feels alien. Have you ever seen a piece of code in the module you work on and thought: “Well, this sure doesn’t look that my writing”? This is something people normally say about handwriting, but I’m sure you’ve experienced this feeling about the writing of code too.

In the case of “auto to stick”, there is a rational aspect to the decision of adopting it, because of Herb Sutter’s arguments you’ve read above.

But in the general case, should we experiment with different styles? Should we change the position of const bewteen const T& or T const&? Should we put the opening brace at the end of line of an if, or at the beginning of the next line?

Style is a people problem

My take is that we should try out new styles as often as possible. A style is a habit and, sometimes, none is objectively better than the other. Or the difference is so small that it hardly matters.

But in a subjective perspective, a certain style may make the code clearer to your eyes and it would be a shame never to try it just because you haven’t started your career by using it.

However, in an existing codeline with other developers working on it with you, everyone changing their coding style every season would make it harder for the team as it would make the codebase a patchwork of different styles.

A better place to try out new styles are your pet projects at home. There, you can try out new styles as often as you like, and decide which ones you prefer.

You can then go back to work with a style to submit to your colleagues, argument why you prefer this one and see if they share your view.

And conversely, learn from the way that others write their code. Get inspiration from good code, and if you see something in an unusual style in the codebase you’re working on, ask the author for the rationale.

Who knows, maybe you have stumbled upon your own future style.

Related articles:

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed