auto seems to be vey powerfull and flexible like a template. It's possible to use it instead of a long name of the left hand side of an initialization operator, e.g, rather than std::vector<std::string>::const_iterator it = ..., we can write auto it = .... Very concise and much less error prone. It can also be used for range-based for loop and looks very neat. Or as the functions parameters type, e.g, rather than the long void foo(std::vector<std::string, std::map<int, std::string>> v1, std::vector<std::string, std::map<int, std::string>> v2, std::vector<std::string, std::map<int, std::string>> v3) it's feasible to write void foo(auto v1, auto v2, auto v3). Much smaller and maintaiable.
PS1: I've heard that it's not good to use the last version due to some reasons. Not sure.
PS2: It's also possible to use using vec = std::vector<std::string, std::map<int, std::string>>; and make the function's signature short like auto: void foo(vec v1, vec v2, vec v3)
How or when do you use auto in your code? And why?
e.g, rather than std::vector<std::string>::const_iterator it = ..., we can write auto it = .... Very concise and much less error prone.
This is one of the situations where I often use auto but not because it's less error prone. Getting the iterator type wrong is often a compilation error so it doesn't bother me much, it's easy to fix. The use of auto might even increase the chance of making mistakes because the type is less visible.
frek wrote:
Or as the functions parameters type, e.g, rather than the long void foo(std::vector<std::string, std::map<int, std::string>> v1, std::vector<std::string, std::map<int, std::string>> v2, std::vector<std::string, std::map<int, std::string>> v3) it's feasible to write void foo(auto v1, auto v2, auto v3). Much smaller and maintaiable.
(Ignoring the fact that std::map<int, std::string> is not a valid allocator)
I disagree here. The first version is self-documenting about what type of arguments it expects. The second version would require you to write comments about what v1, v2 and v3 is supposed to be.
What happens if I pass something that is similar but not quite what you expected? E.g. passing a std::deque<std::unordered_map<long, std::string_view>> when you expect a std::vector<std::map<int, std::string>>. Will that work? Will it give an error at compile time or at runtime? Or is it simply UB?
Note that the second version is actually a template.
It's equivalent to:
I mainly use auto when I want to reduce the amount of typing, and the object being created has a lifetime of a few lines of code. With considerations of readability at a later date.
Saving a few keystrokes isn't worth the "WTH is this code doing?!?" that can happen from time to time.
What do you guys think of the version used in PS2 in my first post?
And I didn't get what you meant by "Ignoring the fact that std::map<int, std::string> is not a valid allocator)".
Whenever possible. In fact, I wish C++ had ML-style type inference, where the compiler is able to deduce the types of almost all names. I also make frequent use of decltype to do, shall we say, type algebra. Proper use of auto and decltype makes refactors way, way easier.
As far as readability is concerned:
1. The intent of the code should be clear just from the interaction between names and functions. The semantics should be defined such that it translates that intent into correct state transformations, regardless of the particular types that are involved. If certain types do have different semantics due to properties intrinsic to their definition, then those types should be annotated explicitly. For example, the behavior of a rational, a binary float, and a decimal float, when divided by 10 could be crucially different in specific circumstances such that the code becomes incorrect if the types are freely switched, even though they all overload operator/().
2. It's not the '80s anymore. If it's necessary to know the actual type of an identifier to understand its semantics, figuring it out is just a matter of hovering a cursor over the identifier in an IDE.
The first template argument specifies the element type. You're saying you want to store std::string elements in the vector. This is fine.
The second template argument specifies the allocator type. If you don't specify an allocator type it will use std::allocator which uses new and delete to allocate and deallocate memory. There are certain requirements that a type must fulfil to be used as an allocator. E.g. it needs to have functions named allocate and deallocate. You have specified that you want to use std::map<int, std::string> as an allocator type but it doesn't fulfil the requirements so it cannot be used as an allocator.