27

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,737
  • 17
  • 31
  • 5
    `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
  • 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

1 Answers1

7

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,737
  • 17
  • 31
  • (This answer seems great but still trying to understand..) Just for future reference: **1)** See [§8.6.4 The range-based for statement] of C++20 standard to understand how range-based for loop works internally. **2)** See @DavidG's comment mentioning 'deduction'. Another example relating deduction(from comments in [another answer](https://stackoverflow.com/a/13242177/10027592)): `int i; auto&& j=i;` is compiled because `auto&&` is **deduced** to be an `int&` (lvalue reference) when assigned an lvalue. (`auto&&` does not become `int&&` because an rvalue reference cannot be bound to an lvalue). – starriet Sep 30 '22 at 10:28
  • "Both `auto&` and `auto&&` are equivalent to `value_type&`" ->Does this also applies to *structured binding* in a range-based for loop? I.e., `for (auto& [k,v] : mymap){...}` and `for (auto&& [k,v] : mymap){...}` are the same? – starriet Sep 30 '22 at 21:48
  • 1
    @starriet I don't see why it wouldn't: https://godbolt.org/z/ebG1Wq61r – LHLaurini Sep 30 '22 at 22:11