What's the difference between & and && in a range-based for loop?
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 valid- Both
auto&
andauto&&
are equivalent tovalue_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);
}
C++11 Range-based for-loop efficiency const auto &i versus auto i
Yes. The same reason if you only ever read an argument you make the parameter const&
.
T // I'm copying this
T& // I'm modifying this
const T& // I'm reading this
Those are your "defaults". When T
is a fundamental type (built-in), though, you generally just revert to const T
(no reference) for reading, because a copy is cheaper than aliasing.
I have a program that I'm developing in which I'm considering making this change throughout, since efficiency is critical in it
- Don't make blind sweeping changes. A working program is better than a fast but broken program.
- How you iterate through your loops probably won't make much of a difference; you're looping for a reason, aren't you? The body of your loop will much more likely be the culprit.
- If efficiency is critical, you want to use a profiler to find which parts of your program are actually slow, rather than guess at parts that might be slow. See #2 for why your guess may be wrong.
What is the advantage of using forwarding references in range-based for loops?
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.
What is the difference between regular for statement and range-based for statement in C++
The difference in your case is, that the first version with iterators, well, uses iterators (that's why cout << i << endl;
is not working), and the second version (the range-based for loop) gives you either a copy, a reference, or const reference.
So this:
for(auto i = vec.begin(); i != vec.end(); i++)
{
cout << i << endl; // should be *i
}
uses iterators (vec.begin()
gives you an iterator to the first element).
Whereas this:
for(auto i : vec)
{
cout << i << endl;
}
uses copies of elements in your vector.
While this:
for(auto& i : vec)
{
cout << i << endl;
}
uses references to your vector elements.
Range-based for loop on a temporary range
Note that using a temporary as the range expression directly is fine, its lefetime will be extended. But for f()[5]
, what f()
returns is the temporary and it's constructed within the expression, and it'll be destroyed after the whole expression where it's constructed.
From C++20, you can use init-statement for range-based for loop to solve such problems.
(emphasis mine)
If range_expression returns a temporary, its lifetime is extended
until the end of the loop, as indicated by binding to the rvalue
reference __range, but beware that the lifetime of any temporary
within range_expression is not extended.This problem may be worked around using init-statement:
for (auto& x : foo().items()) { /* .. */ } // undefined behavior if foo() returns by value
for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK
e.g.
for(auto thing = f(); auto e : thing[5])
std::cout << e << std::endl;
Related Topics
Which Stl Container Should I Use for a Fifo
How to Solve Memory Fragmentation
Practical Uses for the "Curiously Recurring Template Pattern"
Using Hierarchy in Findcontours () in Opencv
Generating a Normal Map from a Height Map
Differencebetween a Static and Const Variable
How to Get a "Codesigned" Gdb on Osx
Initializing Default Values in a Struct
Why Is Statically Linking Glibc Discouraged
How to Avoid Code Duplication Implementing Const and Non-Const Iterators
Converting an Int to Std::String
How to Do Static Linking of Libwinpthread-1.Dll in Mingw
What Is the Default Hash Function Used in C++ Std::Unordered_Map
When Should Your Destructor Be Virtual
Boost_Static_Assert Without Boost