However, my impression is that he’s largely using the existence of templates and polymorphism as arguments that “we don’t really care about type”. I disagree: A template is essentially a generic type description that says something about what types are acceptable. When working with something polymorphic, I’ll prefer ParentClass&, to indicate what kind of interface I’m working with.
Sure, it can be very useful to hide exact type information in order to generalise the code, but I think that’s a weak argument for hiding all type information by default, which is what auto does.
auto doesn’t really hide all type information, but that is besides the point. The point is that in the vast majority of cases you don’t really care about the specific types your code is dealing with, you only care about the properties of the type.
If I write get_descriptors()[some_idx] what is the type returned by get_descriptors()? You can’t know, because nothing tells you, but you know it must be a type that supports random access indexing. If I put the result of the function call in a for loop, it must support iteration. If I return it by move, it must support being moved.
99% of code is like this. Then you have specialised code that might be doing bit wrangling and what not, in which case you must know that you are dealing exactly with, say, an uint32_t and not just something that supports left shift.
For the 99% of cases, auto makes the code simpler, more correct, more robust, more consistent, and shorter to write and read.
The post I replied to in particular got angry at documentation that uses auto. I have written documentation that looks like this
auto my_var = MyConcreteType { /* … */ };
autoconst* x = my_var.get<uint32_t>();
autoconst& y = my_var.get_or<uint32_t>(42);
How would such code benefit from not using auto?
MyConcreteTypemy_var= MyConcreteType {}; just repeats the type, adds zero useful info.
MyConcreteType my_var {}; is inconsistent. Where is the equal sign signifying that this is an assignment?
Similarly, what would you gain by saying uint32_tconst* x = my_var.get<uint32_t>();? Nothing, you just doubled your maintenance when you need to change the type passed to the template parameter.
Similarly, what would you gain by saying uint32_t const* x = my_var.get<uint32_t>();
To be frank: You gain the information that MyConcreteType::get<uint32_t> returns a uint32_t, which I otherwise couldn’t infer from the docs. Of course, I could assume it, based on the template parameter, but I don’t want to go around assuming a bunch of stuff in order to read docs.
Take an example like auto x = my_var.to_reduced_form(), it’s very clear that x is the “reduced form” of my_var, which could be meaningful in itself, but what type is it? I need to know that if I want to do anything with x. Can I do x += 1? If I do, will that modify my_var? Let’s say I want to make a vector of whatever to_reduced_form returns… and so on.
All these questions are very easily answered by MyConcreteTypex= my_var.to_reduced_form(). Now I immediately know that everything I can do with my_var, I can also do with x. This makes me happy, because I need to do less digging, and the code becomes clearer to read.
To answer you: no, x += 1 cannot mutate my_var, because it’s a copy. If you wanted something else you would say auto& or auto*.
And if the type of x is such that even a copy can mutate the original (e.g. maybe it’s a wrapper around an index with an overloaded operator+=() that mutates the original entry in the storage), you are probably working with a specialized system of some kind, since that breaks the semantics of the language, and hopefully you would know from context that you have such cases.
And yes, in such an environment I would see “never use auto for this wrapper type” as a valid additional strategy to help ensure code correctness.
But by and large my direct experience is that using auto “almost always”, as Herb Sutter puts it, is beneficial and doesn’t harm readability or understandability.
Thanks, that was a good read :)
However, my impression is that he’s largely using the existence of templates and polymorphism as arguments that “we don’t really care about type”. I disagree: A template is essentially a generic type description that says something about what types are acceptable. When working with something polymorphic, I’ll prefer
ParentClass&
, to indicate what kind of interface I’m working with.Sure, it can be very useful to hide exact type information in order to generalise the code, but I think that’s a weak argument for hiding all type information by default, which is what
auto
does.auto
doesn’t really hide all type information, but that is besides the point. The point is that in the vast majority of cases you don’t really care about the specific types your code is dealing with, you only care about the properties of the type.If I write
get_descriptors()[some_idx]
what is the type returned byget_descriptors()
? You can’t know, because nothing tells you, but you know it must be a type that supports random access indexing. If I put the result of the function call in a for loop, it must support iteration. If I return it by move, it must support being moved.99% of code is like this. Then you have specialised code that might be doing bit wrangling and what not, in which case you must know that you are dealing exactly with, say, an
uint32_t
and not just something that supports left shift.For the 99% of cases,
auto
makes the code simpler, more correct, more robust, more consistent, and shorter to write and read.The post I replied to in particular got angry at documentation that uses
auto
. I have written documentation that looks like thisauto my_var = MyConcreteType { /* … */ }; auto const* x = my_var.get<uint32_t>(); auto const& y = my_var.get_or<uint32_t>(42);
How would such code benefit from not using
auto
?MyConcreteType my_var = MyConcreteType {};
just repeats the type, adds zero useful info.MyConcreteType my_var {};
is inconsistent. Where is the equal sign signifying that this is an assignment?Similarly, what would you gain by saying
uint32_t const* x = my_var.get<uint32_t>();
? Nothing, you just doubled your maintenance when you need to change the type passed to the template parameter.To be frank: You gain the information that
MyConcreteType::get<uint32_t>
returns auint32_t
, which I otherwise couldn’t infer from the docs. Of course, I could assume it, based on the template parameter, but I don’t want to go around assuming a bunch of stuff in order to read docs.Take an example like
auto x = my_var.to_reduced_form()
, it’s very clear thatx
is the “reduced form” ofmy_var
, which could be meaningful in itself, but what type is it? I need to know that if I want to do anything withx
. Can I dox += 1
? If I do, will that modifymy_var
? Let’s say I want to make avector
of whateverto_reduced_form
returns… and so on.All these questions are very easily answered by
MyConcreteType x = my_var.to_reduced_form()
. Now I immediately know that everything I can do withmy_var
, I can also do withx
. This makes me happy, because I need to do less digging, and the code becomes clearer to read.To answer you: no,
x += 1
cannot mutatemy_var
, because it’s a copy. If you wanted something else you would sayauto&
orauto*
.And if the type of
x
is such that even a copy can mutate the original (e.g. maybe it’s a wrapper around an index with an overloadedoperator+=()
that mutates the original entry in the storage), you are probably working with a specialized system of some kind, since that breaks the semantics of the language, and hopefully you would know from context that you have such cases.And yes, in such an environment I would see “never use auto for this wrapper type” as a valid additional strategy to help ensure code correctness.
But by and large my direct experience is that using auto “almost always”, as Herb Sutter puts it, is beneficial and doesn’t harm readability or understandability.