296

Reading some examples of range based loops they suggest two main ways 1, 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?

cigien
  • 57,834
  • 11
  • 73
  • 112
masoud
  • 55,379
  • 16
  • 141
  • 208

5 Answers5

527

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.
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 37
    Thanks for the great answer. I guess it should also be pointed out that `const auto &x` is equivalent to your third choice. – smg Apr 16 '15 at 15:11
  • @smg `const auto &x` is not equivalent to `auto const &x`, but it's the same syntax. It's just that the latter is clearer and more readable formatting. – mloskot May 12 '15 at 12:11
  • 9
    @mloskot: It is equivalent. (and what do you mean by *"but it's the same syntax"*? The syntax is observably different.) – Nawaz May 12 '15 at 13:25
  • 23
    Missing: `auto&&` when you don't want to make a needless copy, and don't care if you modify it or not, and you want to just work. – Yakk - Adam Nevraumont May 13 '15 at 13:34
  • @Nawaz I mean, the standard does not specify two syntaxes as alternatives (no OR'ed BNF), but describes one where cv-qualifers can be freely mixed in decl-specifier-seq. To me, "equivalent" means close, but not necessarely the same, whereas those two forms are exactly the same. – mloskot May 13 '15 at 14:42
  • 5
    Does the reference distinction apply to copies if you are just working with fundamental types like int/double? –  Jan 30 '16 at 22:45
  • @racarate: "the reference distinction apply to copies"* doesn't make sense to me. What do you mean by that? As for what I said in the answer, applies to ALL types, fundamental as well user-defined types. – Nawaz Jan 31 '16 at 17:30
  • @Nawaz Sorry, that was poorly worded. I'm curious about speed. If I won't be modifying an int is it preferable to work with copies or const references when using a range-based for? Both protect the original value from modification, but which is the more performant approach. –  Jan 31 '16 at 20:13
  • 4
    @racarate: I cannot comment on the *overall* speed, and I think nobody can, without profiling it first. Frankly, I wont base my choice on the speed, rather the clarity of the code. If I want immutability, I'd use `const` for sure. However, whether it would be `auto const &`, or `auto const` has little difference. I'd choose `auto const &` just to be more consistent. – Nawaz Feb 01 '16 at 06:53
  • 1
    Another good reason for using `const auto &` over `const auto` is that the whole point of auto is so that if you change the type you don't have to change the declaration... which means if it was a base type and you make it a big complex class, you still won't have to change it to avoid the copy that it was doing. – UKMonkey Jan 11 '18 at 10:48
  • When would I want to work with copies then? It looks like using `const &` gives you better performance and protects from modification. – Konstantin Milyutin Mar 25 '20 at 15:22
  • 3
    @damluar: What if you want to do something which mutates the values and you want the mutations to be local to the loop? You need _copies_ then! – Nawaz Mar 25 '20 at 17:31
  • I really wanted to up vote this, but i also don't wanna effect the perfect '420' score. – lycuid Jan 23 '21 at 11:10
38

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)
    ....
Mr.C64
  • 41,637
  • 14
  • 86
  • 162
5

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 &.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
1

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 &.

Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • 3
    @BenjaminLindley: So I can infer that you would also argue against `const_iterator` in a for loop? How do you ensure that you don't change the original items from a container when you iterate over it? – Nawaz Mar 02 '13 at 16:42
  • @Nawaz: In a for loop, I use auto. In C++03, yes, I would use `const_iterator`, but not to ensure that I don't change the items in the container. I would use `const_iterator` because otherwise it won't compile for const containers. I don't need `const_iterator` to ensure that I don't change items in a container, because I don't write loop bodies so large that their purpose is not obvious at a glance. – Benjamin Lindley Mar 02 '13 at 16:51
  • 2
    @BenjaminLindley: Why would your code not compile without `const_iterator`? Let me guess, the container which you iterator over, is `const`. But why is it `const` to begin with? Somewhere you're using `const` to ensure what? – Nawaz Mar 02 '13 at 16:54
  • @Nawaz: Because the function received it that way. – Benjamin Lindley Mar 02 '13 at 16:55
  • @BenjaminLindley: Why not receive it as non-const reference? – Nawaz Mar 02 '13 at 16:56
  • @Nawaz: So that const objects can be passed to the function. – Benjamin Lindley Mar 02 '13 at 16:57
  • @BenjaminLindley: Which functions? Those functions are taking argument `const` reference? But why? – Nawaz Mar 02 '13 at 16:58
  • @Nawaz: Didn't my last comment answer that? You take an argument by const reference so that const objects can be passed to the function. If you take the argument by non-const reference, const objects cannot be passed to it. – Benjamin Lindley Mar 02 '13 at 16:59
  • @BenjaminLindley: Why those objects are `const` to begin with? If you make them non-const, then you don't have to receive arguments by `const` reference. Basically, I'm asking why use `const` at all? – Nawaz Mar 02 '13 at 17:01
  • @Nawaz: Who knows? There are a multitude of reasons to make an object const. That's the caller's purview, not mine. I'm the one writing the function which takes the object. And if my function doesn't modify the object, there is no reason to arbitrarily restrict it so that only non-const objects can be passed. – Benjamin Lindley Mar 02 '13 at 17:03
  • @BenjaminLindley: In what case *you* make objects `const`? And why? – Nawaz Mar 02 '13 at 17:23
  • 11
    Advantage of making objects `const` is like the advantages of using `private`/`protected` in classes. It avoids further mistakes. – masoud Mar 02 '13 at 17:46
  • I think the implementation of `SafePop()` is silly only because it does not take a reference to `const` in the first place. But supposing some silly boss told the programmer "This function HAS to have this signature, all right? I don't care if you don't like it!", then I would say the programmer did a good job. – Andy Prowl Mar 02 '13 at 21:34
  • @AndyProwl: `v.pop_back()` won't compile if the function takes a reference to const. – Benjamin Lindley Mar 02 '13 at 21:38
  • 2
    @BenjaminLindley: Oh, I overlooked that. Then in this case, the implementation is silly as well. But if that function did not modify `v`, the implementation would be fine IMO. And if the loop never modifies the objects it iterates through, it seems right to me to have a ref to `const`. I see your point though. Mine is that the loop represents a code unit pretty much like the function does, and *within that unit* there is no instruction which needs to modify the value. Therefore, you can "tag" the whole unit as `const`, as you would do with a `const` member function. – Andy Prowl Mar 02 '13 at 21:42
  • 4
    So you think `const &` for range-based is silly, because you wrote an irrelevant imaginary example of an imaginary programmer who did something silly with _cv_-qualification? ...OK then – underscore_d Feb 26 '16 at 15:02
  • @underscore_d: Why do you think what that imaginary programmer did was silly? – Benjamin Lindley Feb 26 '16 at 15:41
  • Because they have 2 different references to the same object (best avoided in and of itself) and specifically with 2 different cv-qualifications. Range-based for doesn't have that, but it _does_ allow us to avoid accidentally altering a should-be-`const` object, just as a `const &` parameter would. Why do you think this is not useful? I'm glad you're a good enough programmer not to need the compile error, but otherwise it's a really good safeguard and indication of intent. – underscore_d Feb 26 '16 at 15:54
  • 1
    Look, I write code that a _lot_ of other people are going to look at and edit. In software engineering, there is a concept of the [7 "-ilities"](http://codesqueeze.com/the-7-software-ilities-you-need-to-know) and this one is [maintainability](https://en.wikipedia.org/wiki/Maintainability#Software_engineering). [Const correctness](https://isocpp.org/wiki/faq/const-correctness) is an important concept in maintainability and that's what this is about. – Daniel Santos Jan 25 '17 at 01:19
1

I would consider

for (auto&& o : range_expr) { ...}

or

for (auto&& o : std::as_const(range_expr)) { ...}

it always works.

and beware of possible Temporary range expression pitfalls.

In C++20 you have something like

for (T thing = foo(); auto& x : thing.items()) { /* ... */ }
cigien
  • 57,834
  • 11
  • 73
  • 112
rnd_nr_gen
  • 2,203
  • 3
  • 36
  • 55