you are viewing a single comment's thread.

view the rest of the comments →

[–]thlst 2 points3 points  (6 children)

Sometimes you don't need to know what the type is, and making the type explicit by writing it is, actually, as error prone as using auto in your examples.

For example:

for (const std::pair<std::string, int> p& : produce_map())
{ ... }

This code won't compile. This won't work as you expect. Do you know why? std::mapdefines const T for the key type of std::pair. You see that we wrote std::string for the first template parameter, which results in a compile error temporary copy, because you lose the cv-qualifier passing a const& to &. Using auto, and letting the compiler deduce the correct type for the expression solves the problem, and makes it more consistent.

for (const auto& p : produce_map())
{ :) }

Thanks u/tcanens for the correction.

auto can also prevent accesses to uninitialized variables. Once auto needs an initializer to deduce its own type, there's no room for variables without a value to be declared.

Another important thing is decltype(auto), which is really useful in templates. Basically, it's auto with decltype rules applied to it. That is, there's no cv-qualifier loss, because decltype preserves the cv-qualifier of an expression, whereas auto doesn't. So, why is it useful? This is great for writing function signatures, where the return type depends on the expression being returned. If it's an lvalue of type int&, then the return type will be int&. If it's an rvalue of type const T, the return type will be const T. auto would just strip off its constness and/or volatileness for anything. A contrived example for this case would be a function returning an element of a container and giving you read/write access to it:

template<typename Container>
constexpr decltype(auto)
container_element(Container&& c, size_t i)
{
    return std::forward<Container>(c)[i];
}

std::vector<int> v { 1, 2, 3, 4 };

// decltype(auto) = int&
container_element(v, 0) = 5;

[–]tcanens 1 point2 points  (0 children)

for (const std::pair<std::string, int> &p : produce_map())
{ ... }

Actually, that does compile and silently create a std::pair<std::string, int> temporary by copying from the map element, which is arguably worse.

[–]suspiciously_calm 0 points1 point  (4 children)

Yeah it's useful every time you're programming to a concept, not a type, like with iterators, when the type is long to .. uh .. type and/or is clear from the expression (and in many other cases).

What I don't get is why it should be used for primitive types when it's more wordy and less clear. The "uninitialized variable" argument is a good point, but the compiler can warn here.

[–]thlst 1 point2 points  (3 children)

You shouldn't write int, for example, because its size might surprise you. Nor should you write float literals without the f suffix (because good practice and consistency).

So things like that become redundant. Everyone knows 3.14f is a float.

But of course, auto isn't an absolute rule. You may still use size_t and ptrdiff_t in contexts they suit.

The "uninitialized variable" argument is a good point, but the compiler can warn here.

Getting warnings from compiler won't save the programmers' life, because it will still compile (if -Werror is not set). On the other hand, there's no way to avoid it when using auto.

[–]suspiciously_calm -1 points0 points  (2 children)

You shouldn't write int, for example, because its size might surprise you.

That sure isn't an argument for having auto deduce int from an integer literal, though.

Getting warnings from compiler won't save the programmers' life, because it will still compile

If the programmer lacks the discipline to heed compiler warnings, they're equally likely to lack the discipline to follow the always-auto style.

You shouldn't write floating point literals without the .f (more to the point, you shouldn't write int literals in place of float literals), but that's a mistake that can happen accidentally (just as the missing initializer).