23

I'm wondering what's the difference between for (auto& i : v) and for (auto&& i : v) in a range-based for loop like in this code:

#include <iostream>
#include <vector>

int main() 
{
    std::vector<int> v = {0, 1, 2, 3, 4, 5};

    std::cout << "Initial values: ";

    for (auto i : v)    // Prints the initial values
        std::cout << i << ' ';
    std::cout << '\n';

    for (auto i : v)    // Doesn't modify v because i is a copy of each value
        std::cout << ++i << ' ';
    std::cout << '\n';

    for (auto& i : v)   // Modifies v because i is a reference
        std::cout << ++i << ' ';
    std::cout << '\n';

    for (auto&& i : v)  // Modifies v because i is a rvalue reference (Am I right?)
        std::cout << ++i << ' ';
    std::cout << '\n';

    for (const auto &i : v) // Wouldn't compile without the /**/ because i is const
        std::cout << /*++*/i << ' ';
    std::cout << '\n';

}

The output:

Initial values: 0 1 2 3 4 5
1 2 3 4 5 6
1 2 3 4 5 6
2 3 4 5 6 7
2 3 4 5 6 7

Both seem to do the same thing here but I'd like to know what's the difference between for (auto& i : v) and for (auto&& i : v) in this code.

LHLaurini
  • 1,270
  • 13
  • 28
  • 4
    `auto` uses template argument deduction rules. So `auto` by itself deduces the type by value, `auto&` deduces by reference, and `auto&&` is not an rvalue reference but rather uses reference collapsing rules to deduce the type. – David G Mar 28 '15 at 17:59
  • @0x499602D2 Thanks for your answer. I got the "deduces the type by value" part but what does deduce by reference means? Is there something about that I can read that can help me understand it? – LHLaurini Mar 28 '15 at 18:12
  • 2
    Simply put, `auto&` is an lvalue-reference so it requires an lvalue for its initializer. The elements of the vector can be iterated over as lvalues, so it works. It's also useful when you want to prevent copying each element. This video should explain everything else - https://vimeo.com/97344493 – David G Mar 28 '15 at 18:17
  • @0x499602D2 Okay. This is what I think I got: using only `auto` would copy the object to `i` (already knew this one). And there's no difference between `&` and `&&` in this case because there aren't `rvalue`s, only `lvalue`s. Is this right? – LHLaurini Mar 28 '15 at 18:46
  • 1
    Yes, that's correct. But if the elements being iterated are rvalues, `auto` by itself will allow a move. And it can only bind to `auto&&` because rvalues cannot bind to lvalue-references. – David G Mar 28 '15 at 18:49
  • @0x499602D2 Thanks. Also, how could possibly the elements being iterated be rvalues? Maybe a `std::initializer_list`? – LHLaurini Mar 28 '15 at 18:53
  • @0x499602D2 PS: Also, you could (and should) post this as an answer. – LHLaurini Mar 28 '15 at 19:00
  • 2
    @LHLaurini `std::vector` is the most notorious example. – T.C. Mar 28 '15 at 19:04
  • 1
    You need a special kind of iterator like what `std::vector` has. It can't just be done automatically, even if you do `for (auto& x : {1, 2, 3})` the elements are still iterated over as lvalues. – David G Mar 28 '15 at 19:07
  • @T.C. Thanks for your answer. – LHLaurini Mar 28 '15 at 19:35
  • 1
    Ah. So the mysterious and unique phrase on http://en.cppreference.com/w/cpp/language/range-for, "deduction to forwarding reference," just means reference collapsing rules. – Camille Goudeseune Sep 25 '15 at 22:08

2 Answers2

3

This answer will probably answer your question, the most relevant part is the following:

By using auto&& var = you are saying: I will accept any initializer regardless of whether it is an lvalue or rvalue expression and I will preserve its constness.

auto         => will copy the element, but a reference is more efficient
auto&        => will bind to modifiable lvalues
const auto&  => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind to rvalues
Beginner
  • 4,992
  • 4
  • 28
  • 69
1

7 years after I asked this question, I feel qualified to provide a more complete answer.

I'll start by saying that the code I chose back then is not ideal for the purpose of the question. That's because there is no difference between & and && for the example.

Here's the thing: both

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

for (auto& i : v)
{
    std::cout << ++i << ' ';
}

std::cout << '\n';

and

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

for (auto&& i : v)
{
    std::cout << ++i << ' ';
}

std::cout << '\n';

are equivalent.

Here's proof:

#include <vector>

std::vector<int> v;

void f()
{
    for (auto& i : v)
    {
        static_assert(std::is_same<decltype(i), int&>::value);
    }

    for (auto&& i : v)
    {
        static_assert(std::is_same<decltype(i), int&>::value);
    }
}

But why?

Like David G said in the comments, a rvalue reference to a lvalue reference becomes a lvalue reference due to reference collapsing, eg

#include <type_traits>
using T1 = int&;
using T2 = T1&&;
static_assert(std::is_same<T1, T2>::value);

Note that this, however, is different:

for (int&& i : v)
{
    // ...
}

and will fail, since a rvalue reference can't bind to a lvalue. Reference collapsing doesn't apply to this case, since there is no type deduction.

TLDR: for the standard containers, the difference between & and && in a range-based for loop is:

  • value_type& is valid
  • value_type&& is not valid
  • Both auto& and auto&& are equivalent to value_type&

Now let's try the opposite: an iterable object that returns rvalues.

#include <iostream>

struct Generated
{
    int operator*() const
    {
        return i;
    }

    Generated& operator++()
    {
        ++i;
        return *this;
    }

    bool operator!=(const Generated& x) const
    {
        return i != x.i;
    }

    int i;
};

struct Generator
{
    Generated begin() const { return { 0 }; }
    Generated end() const { return { 6 }; }
};

int main()
{
    Generator g;

    for (const auto& i : g)
    {
        std::cout << /*++*/i << ' ';
    }
    std::cout << '\n';

    for (auto&& i : g)
    {
        std::cout << ++i << ' ';
    }
    std::cout << '\n';
}

Here, auto& doesn't work, since you can't bind a non-const lvalue to a rvalue.

Now we actually have const int& and int&&:

Generator g;

for (const auto& i : g)
{
    static_assert(std::is_same<decltype(i), const int&>::value);        
}

for (auto&& i : g)
{
    static_assert(std::is_same<decltype(i), int&&>::value); 
}
LHLaurini
  • 1,270
  • 13
  • 28