C++11 Range-Based For-Loop Efficiency "Const Auto &I" Versus "Auto I"

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

  1. Don't make blind sweeping changes. A working program is better than a fast but broken program.
  2. 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.
  3. 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 difference between auto and auto&?

Auto, for(auto x : range): This usage will create a copy of each element of the range.

Auto &, for(auto& x : range): when you want to modify the elements in range (without proxy class reference processing), use auto&.

ii is a reference, so in the loop body, if ii is modified, the corresponding element in a will also be modified.

jj is not a reference. In each loop, it is a copy of the corresponding element. Its modification will not affect the corresponding element in a. Since each loop creates a copy, it will bring system overhead.

If you want to ensure that the data in a is not modified and you want to improve the efficiency, you can use the form const auto & ii : a.

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 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);
}

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.

Read-only ranged based for

Do not overthink this. In C and C++ there is "AS IF RULE" which causes that any version of your example doesn't lead to any changes in resulting machine code.

See this godbolt: https://godbolt.org/z/3rnWrr Explenation: note each compiler command line arguments is defining VERSION macro, to provide auto, auto& or auto const &.

So basically if vector contains simple type (like build ins) just use auto since it is more convenient. In other case with complex types, you need to think about it a bit more and decide between auto and auto&. If have doubts do some measurements or check assembly.

Range for statement [ c++11 ]

The first version gets a reference to the element of the vector, thus allowing you to modify the element in the vector.

The second version gets the element "by value", which involves a copy and does not allow you to modify the value.

It is pretty much the same as with function parameters and might make a difference when working with const qualifiers (might require the second version) or large objects which do not allow copies (require first version) or where copies are expensive (prefer first version).

In your case it doesn't make much of a difference, since you don't modify i and a copy of an int is cheap. The compiler might even optimize the copy away.

Is there any advantage of using a range for loop rather than an iterator?

Iterators predate range-based for loops, so they used to be the only of these two alternatives available. Nowadays, range-based for has mostly replaced iterators when dealing with a simple loop.

However, iterators can be used in various other contexts as well. For example, you can do std::sort(v.begin(), std::next(v.begin(), 5)) to sort the first five elements of a vector while leaving the rest of it alone.

Going back to iterating over a whole container:

  • If you can accomplish what you want with a range-based for, then it leads to more legible code, so they are preferable by default.

  • If you need iterators for some reason, such as using an algorithm that requires them, or because you need to jump ahead or back while iterating, then use those instead.

Also: In the later case, you can/should still use auto when declaring the iterator:

for(auto it = just_a_vec.begin(); it < just_a_vec.end(); it++) {
}

Edit: as asked: here's a simple, if a bit contrived, example where an iterator-based loop can still be useful:

// adds all values in the vector, but skips over twos values when encountering a 0
// e.g.: {1,2,0,4,5,2} => 5
int my_weird_accum(const std::vector<int>& data) {
int result = 0;

for(auto it = data.begin(); it != data.end(); ++it) {
auto v = *it;
result += v;

if(v == 0) {
// skip over the next two
assert(std::distance(it, data.end()) > 2);
std::advance(it, 2);
}
}
return 0;
}

Forcing auto to be a reference type in a range for loop

A minimal auto reference

The loop can be declared as follows:

for (auto& it : foo) {
// ^ the additional & is needed
/*ToDo - Operate on 'it'*/
}

This will allow it to be a reference to each element in foo.

There is some debate as to the "canonical form" of these loops, but the auto& should do the trick in this case.

General auto reference

In a more general sense (outside the specifics of the container), the following loop works (and may well be preferable).

for (auto&& it : container) {
// ^ && used here
}

The auto&& allows for bindings to lvalues and rvalues. When used in a generic or general (e.g. template situation) this form may strike the desired balance (i.e. references, copies, prvalue/xvalue returns (e.g. proxy objects) etc.).

Favour the general auto&&, but if you have to be specific about the form, then use a more specific variation (e.g. auto, auto const& etc.).

Why is auto&& better?

As noted in other answers here and the comments. Why is auto&& better? Simply it will do what you think it should in most cases, see this proposal and its update.

As always, Scott Meyers' blog about this also makes for a good read.

Range-based for loops in C++11 segfault, but not with regular for loop

EDIT:
I am a liar.

Tree_NodeT pointers were being created, but not initialized to nullptr somewhere in the Build_Tree_List function. Thus, I got a vector back where some of the pointers were pointing to valid memory, and others were just newly constructed pointers not set to null or given any address. It is still interesting that the first loop was able to handle this without crashing, while the second one segfaulted.



Related Topics



Leave a reply



Submit