19

clang has begun implementing terse ranged-based for loops from n3994. Often when introducing ranged-based for loops, we see code in the form of for (auto & v : vector) to avoid unnecessary copying. It seems that n3994 is proposing that for (auto && v : vector) is superior in every way. I have a few questions:

  1. What advantages does the latter form offer over the former? Why do we usually go with auto & instead of auto && if the latter is clearly advantageous?
  2. Is making the new ranged-for based loop equivalent to auto && going to break existing code? Will it make an actual impact on new code?
  3. Wouldn't this introduce a gotcha to beginners that their code is actually equivalent to auto &&?
user3972004
  • 193
  • 5

2 Answers2

10

What advantages does the latter form offer over the former?

In the form for(auto& v : vector), the type of v is deduced as an lvalue reference to the type obtained by dereferencing the container's iterator type. This means that if the result of dereferencing the iterator is an rvalue (think std::vector<bool> which returns a proxy type representing a reference to a single bool value), then the code will fail to compile, because an lvalue reference cannot bind to an rvalue.

When you write for(auto&& v : vector), this is a universal reference, meaning the type of v will either be deduced as an rvalue reference in the case described above; or as an lvalue reference in the usual case where dereferencing the iterator returns a reference to the container's element. So it works in the vector<bool> case as well. That's why that form should be preferred if you plan on modifying the elements you're iterating over within the loop.

Why do we usually go with auto& instead of auto&& if the latter is clearly advantageous?

You shouldn't. The only downside I can think of auto&& is that it doesn't guarantee that the changes you make to the elements will necessarily propagate back to the container, but that's indicative of a broken design, and I don't think it's worth protecting against.

Is making the new ranged-for based loop equivalent to auto && going to break existing code?

I don't see how it can break existing code because the old syntax will continue to function as it does today. But if you mean replacing existing code with the new syntax, then it might have an effect if the one you're replacing is the auto const& form. See this example. Notice how the auto const& version calls the const member function, while the other two call the non-const version? Replacing the first one with the terse version will change the member function being called.

Will it make an actual impact on new code?

Again, it's no different from old code that uses auto&& today, so that'll see no difference. If you use it in places where you don't intend to modify the elements, then the compiler will no longer stop you from doing that accidentally, and you might call a different overload, as shown in the example above.

Wouldn't this introduce a gotcha to beginners that their code is actually equivalent to auto &&?

I'm not sure I understand what you mean by this, but if you're asking whether beginners will write code without knowing, or understanding the intricacies of reference collapsing, then yes, it's possible they will. But that is one of the stated goals of the paper you've linked to. The argument is that you can stay away from teaching those difficult concepts right at the get go, but introduce beginners to a single syntax for range-based for loops that works with everything. As far as that is concerned, I think the syntax has merit, but looking at it from a const correctness perspective, I frown upon it because I'd rather use auto const& if I want read-only access to the elements, and then the terse syntax seems asymmetrical.

Praetorian
  • 100,267
  • 15
  • 224
  • 307
  • That pesky `vector` again. I think I was bitten by `auto& v: vector` once when using `vector`. – R Sahu Aug 24 '14 at 05:01
  • W/r/t the last point, I suppose they could introduce something like `for(const elem : range)`... – T.C. Aug 24 '14 at 05:12
  • @T.C. That would be nice. Stephan even lists that exact syntax as a possibility, but for some reason, doesn't include it in the proposal. – Praetorian Aug 24 '14 at 05:16
  • " it doesn't guarantee that the changes you make to the elements will necessarily propagate back to the container, but that's indicative of a broken design" -- should the Container requirements be changed so that this is guaranteed, or is there a good technical reason why they can't be? – Steve Jessop Aug 24 '14 at 11:26
8

What advantages does the latter form offer over the former? Why do we usually go with auto & instead of auto && if the latter is clearly advantageous?

auto & doesn't work if dereferencing the iterator returns proxy objects rather than actual references, as you would be attempting to bind a non-const lvalue reference to a temporary. The standard example is the abomination known as std::vector<bool>; dereferencing its iterator returns a proxy object of type std::vector<bool>::reference that represents a single bit in the vector. Since most iterators return actual references, you don't run into this problem often.

Is making the new ranged-for based loop equivalent to auto && going to break existing code? Will it make an actual impact on new code?

No, because the new syntax, for(elem : range), won't compile in existing code.

Wouldn't this introduce a gotcha to beginners that their code is actually equivalent to auto &&?

Why would it be a gotcha? auto && has the benefit that it actually works for everything. One might argue that not having to teach beginners all the details about type deduction and reference collapsing etc. is actually a plus, as it makes the language easier to learn.

T.C.
  • 123,516
  • 14
  • 264
  • 384