Reputation: 2570
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.
Upvotes: 29
Views: 6721
Reputation: 2570
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 validvalue_type&&
is not validauto&
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);
}
Upvotes: 9