Range based loop: get item by value or reference to const?

Reading some examples of range based loops they suggest two main ways1, 2, 3, 4

std::vector<MyClass> vec;

for (auto &x : vec)
{
  // x is a reference to an item of vec
  // We can change vec's items by changing x 
}

or

for (auto x : vec)
{
  // Value of x is copied from an item of vec
  // We can not change vec's items by changing x
}

Well.

When we don't need changing vec items, IMO, Examples suggest to use second version (by value). Why they don't suggest something which const references (At least I have not found any direct suggestion):

for (auto const &x : vec) // <-- see const keyword
{
  // x is a reference to an const item of vec
  // We can not change vec's items by changing x 
}

Isn't it better? Doesn't it avoid a redundant copy in each iteration while it's a const?


Solution 1:

If you don't want to change the items as well as want to avoid making copies, then auto const & is the correct choice:

for (auto const &x : vec)

Whoever suggests you to use auto & is wrong. Ignore them.

Here is recap:

  • Choose auto x when you want to work with copies.
  • Choose auto &x when you want to work with original items and may modify them.
  • Choose auto const &x when you want to work with original items and will not modify them.

Solution 2:

If you have a std::vector<int> or std::vector<double>, then it's just fine to use auto (with value copy) instead of const auto&, since copying an int or a double is cheap:

for (auto x : vec)
    ....

But if you have a std::vector<MyClass>, where MyClass has some non-trivial copy semantics (e.g. std::string, some complex custom class, etc.) then I'd suggest using const auto& to avoid deep-copies:

for (const auto & x : vec)
    ....

Solution 3:

When we don't need changing vec items, Examples suggest to use first version.

Then they give a wrong suggestion.

Why they don't suggest something which const references

Because they give a wrong suggestion :-) What you mention is correct. If you only want to observe an object, there is no need to create a copy, and there is no need to have a non-const reference to it.

EDIT:

I see the references you link all provide examples of iterating over a range of int values or some other fundamental data type. In that case, since copying an int is not expensive, creating a copy is basically equivalent to (if not more efficient than) having an observing const &.

This is, however, not the case in general for user-defined types. UDTs may be expensive to copy, and if you do not have a reason for creating a copy (such as modifying the retrieved object without altering the original one), then it is preferable to use a const &.

Solution 4:

I'm going to be contrary here and say there is no need for auto const & in a range based for loop. Tell me if you think the following function is silly (not in its purpose, but in the way it is written):

long long SafePop(std::vector<uint32_t>& v)
{
    auto const& cv = v;
    long long n = -1;
    if (!cv.empty())
    {
        n = cv.back();
        v.pop_back();
    }
    return n;
}

Here, the author has created a const reference to v to use for all operations which do not modify v. This is silly, in my opinion, and the same argument can be made for using auto const & as the variable in a range based for loop instead of just auto &.