133

const auto& would suffice if I want to perform read-only operations. However, I have bumped into

for (auto&& e : v)  // v is non-const

a couple of times recently. This makes me wonder:

Is it possible that in some obscure corner cases there is some performance benefit in using forwarding references, compared to auto& or const auto&?

(shared_ptr is a suspect for obscure corner cases)


Update Two examples that I found in my favorites:

Any disadvantage of using const reference when iterating over basic types?
Can I easily iterate over the values of a map using a range-based for loop?

Please concentrate on the question: why would I want to use auto&& in range-based for loops?

Nicol Bolas
  • 378,677
  • 53
  • 635
  • 829
Ali
  • 51,545
  • 25
  • 157
  • 246

3 Answers3

108

The only advantage I can see is when the sequence iterator returns a proxy reference and you need to operate on that reference in a non-const way. For example consider:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto& e : v)
        e = true;
}

This doesn't compile because rvalue vector<bool>::reference returned from the iterator won't bind to a non-const lvalue reference. But this will work:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto&& e : v)
        e = true;
}

All that being said, I wouldn't code this way unless you knew you needed to satisfy such a use case. I.e. I wouldn't do this gratuitously because it does cause people to wonder what you're up to. And if I did do it, it wouldn't hurt to include a comment as to why:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    // using auto&& so that I can handle the rvalue reference
    //   returned for the vector<bool> case
    for (auto&& e : v)
        e = true;
}

Edit

This last case of mine should really be a template to make sense. If you know the loop is always handling a proxy reference, then auto would work as well as auto&&. But when the loop was sometimes handling non-proxy references and sometimes proxy-references, then I think auto&& would become the solution of choice.

Howard Hinnant
  • 179,402
  • 46
  • 391
  • 527
  • 4
    On the other hand, there's no explicit _disadvantage_, is there? (Aside from potentially confusing people, which I don't think is much worth mentioning, personally.) – ildjarn Oct 29 '12 at 22:55
  • That's an interesting point. Presumably there's no way of encouraging range-`for` to prefer `T::const_iterator`? I believe `begin()` and `end()` are used. Seems like a bit of an oversight, except that you can use rvalue refs. So yeah maybe this is the answer. – Lightness Races in Orbit Oct 29 '12 at 23:00
  • 10
    Another term for writing code that unnecessarily confuses people is: writing confuscated code. It is best to make your code as simple as possible, but no simpler. This will help keep the bug count down. That being said, as && becomes more familiar, then maybe 5 years from now people will come to expect an auto&& idiom (assuming it actually does no harm). I don't know if that will happen or not. But simple is in the eye of the beholder, and if you're writing for more than just yourself, take your readers into account. – Howard Hinnant Oct 29 '12 at 23:02
  • 9
    I prefer `const auto&` when I want the compiler to help me check that I don't accidentally modify the elements in the sequence. – Howard Hinnant Oct 29 '12 at 23:04
  • 2
    @LightnessRacesinOrbit: `template T const& as_const(T const& v){ return v; }` and `for(auto& e : as_const(v)) ...`. – Xeo Oct 29 '12 at 23:07
  • @HowardHinnant: Ah, but `const T::iterator` is not `T::const_iterator`, right? i.e. does that actually work in this case? I suppose that's a different question. Looks like "yes" but I don't know why. – Lightness Races in Orbit Oct 29 '12 at 23:08
  • 33
    I personally like to use `auto&&` in generic code where I need to modify the elements of the sequence. If I don't, I'll just stick to `auto const&`. – Xeo Oct 29 '12 at 23:08
  • 8
    @Xeo: +1 It is because of enthusiasts like yourself, constantly experimenting and pushing for better ways of doing things, that C++ continues to evolve. Thank you. :-) – Howard Hinnant Oct 29 '12 at 23:13
  • 1
    @LightnessRacesinOrbit `std::begin` will delegate to calling member `begin`, which is overloaded on `const`. – Luc Danton Oct 30 '12 at 03:54
  • Why isn't `auto&& e` an rvalue and therefore not possible to be assigned to? it's literally appearing on the left hand side here.. – Jonathan. Jul 07 '16 at 11:19
  • @Jonathan.: 7th paragraph of this section: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#More%20on%20A&& "Even though named rvalue references ..." – Howard Hinnant Jul 07 '16 at 14:31
  • 1
    @HowardHinnant, thanks, i justed watched a presentation from Scott Meyers about this and it was immensely helpful with this and realising I'd misunderstood rvalue vs rvalue reference. I don't understand why universal references weren't actually a separate thing, part of the standard with a new token like `&&&`? – Jonathan. Jul 07 '16 at 15:09
  • 1
    @Jonathan.: It was a judgment call made at the time: Kill two birds with one stone. – Howard Hinnant Jul 07 '16 at 16:10
  • 1
    The top example compiles just fine in VS2015u2. – Dmitri Nesteruk Jul 15 '16 at 10:31
  • @DmitriNesteruk: With warnings on, it says it uses an extension to compile it. It is a dangerous extension whose main use is backwards compatibility with pre-C++98 code. – Howard Hinnant Jul 15 '16 at 14:10
  • @HowardHinnant hold on, what would that extension actually do? cheat `auto&` into `auto&&` behind our back? – Dmitri Nesteruk Jul 15 '16 at 15:23
  • @DmitriNesteruk: I don't really know. Almost everything I know about VS, I glean from here: http://webcompiler.cloudapp.net – Howard Hinnant Jul 15 '16 at 16:03
  • 2
    "[...] then maybe 5 years from now people will come to expect an auto&& idiom [...]" 7 years later for(auto&& x : y) is still very confusing and definitely not an expected idiom. – mrexodia Jun 24 '19 at 18:37
26

Using auto&& or universal references with a range-based for-loop has the advantage that you captures what you get. For most kinds of iterators you'll probably get either a T& or a T const& for some type T. The interesting case is where dereferencing an iterator yields a temporary: C++ 2011 got relaxed requirements and iterators aren't necessarily required to yield an lvalue. The use of universal references matches the argument forwarding in std::for_each():

template <typename InIt, typename F>
F std::for_each(InIt it, InIt end, F f) {
    for (; it != end; ++it) {
        f(*it); // <---------------------- here
    }
    return f;
}

The function object f can treat T&, T const&, and T differently. Why should the body of a range-based for-loop be different? Of course, to actually take advantage of having deduced the type using universal references you'd need to pass them on correspondingly:

for (auto&& x: range) {
    f(std::forward<decltype(x)>(x));
}

Of course, using std::forward() means that you accept any returned values to be moved from. Whether objects like this makes much sense in non-template code I don't know (yet?). I can imagine that using universal references can offer more information to the compiler to do the Right Thing. In templated code it stays out of making any decision on what should happen with the objects.

Dietmar Kühl
  • 141,209
  • 12
  • 196
  • 356
8

I virtually always use auto&&. Why get bitten by an edge case when you don't have to? It's shorter to type too, and I simply find it more... transparent. When you use auto&& x, then you know that x is exactly *it, every time.

Puppy
  • 138,897
  • 33
  • 232
  • 446
  • 32
    My problem is that your are giving up `const`-ness with `auto&&` if `const auto&` suffices. The question asks for the corner cases where I can get bitten. What are the corner cases which have not been mentioned by Dietmar or Howard yet? – Ali Oct 30 '12 at 20:19
  • 3
    Here's a way to get bitten if you _do_ use `auto&&`. If the type you are capturing should be moved in the body of the loop (for instance), and is changed to a later date so it resolves to a `const&` type, your code will silently continue functioning, but your moves will be come copies. This code will be very deceptive. If, however, you explicitly specify the type as an r-value reference, whoever changed the container type will get a compilation error because you really really wanted these objects moved and not copied... – cyberbisson Apr 10 '19 at 20:03
  • @cyberbisson I just came across this: how can I enforce an rvalue reference without explicitly specify the type? Something like `for (std::decay_t&& e: v)`? I guess there is a better way ... – Jerry Ma Apr 10 '19 at 22:25
  • @JerryMa It depends on what you _do_ know about the type. As mentioned above, `auto&&` will give a "universal reference", so anything other than that will get you a more specific type. If `v` is assumed to be a `vector`, you could do `decltype(v)::value_type&&`, which is what I assume you wanted by taking the result of `operator*` on an iterator type. You could also do `decltype(begin(v))::value_type&&` to check the iterator's types instead of the container. If we have so little insight into the type, though, we might consider it a little clearer to just go with `auto&&`... – cyberbisson Apr 11 '19 at 17:06