0

What would be a better way (performance) to iterate primitive type container with ranged for (for reading elements values) - read elements by ref or by value?

std::vector<int> v;
for (const auto e : v) { std::cout << e; }

or

for (const auto& e : v) { std::cout << e; }

There is: Is it counter-productive to pass primitive types by reference?

Wondering if these 2 things (passing and iterating by ref and value) might be somehow related.

Another note: I do recognize what is the difference between access by ref, const-ref and value as well as copying values - I only interested in what way would perform better for READ-ONLY.

3 Answers3

2

For small, primitive types, if you don't want to modify the elements of the container, then it's really a matter of style, although for such 'read-only' access, you're probably safer using the by-value approach (which will actually prevent any unintended modification of the 'originals' - although a const qualifier on the reference will also prevent that).

However, if you do want to modify the contained elements, then you will need to iterate using a reference variable, as shown in the below code sample:

#include <iostream>
#include <vector>

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

    for (auto e : v) ++e;                   // By value: takes copies
    for (auto f : v) std::cout << f << " "; // Elements unchanged
    std::cout << std::endl;

    for (auto& e : v) ++e;                  // By reference: refers to actual elements
    for (auto f : v) std::cout << f << " "; // Elements incremented
    std::cout << std::endl;

//  for (const auto& e : v) ++e;            // const reference: Compiler error!

    return 0;
}
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • 1
    Note that one difference between by-value and by-const-reference is that the latter will give you compiler errors if you try to modify the iterated values, whereas the former will not. This may or may not be desirable. – Adrian Mole Dec 02 '20 at 09:13
  • note that OP edited, now the question is between `const auto` and `const auto&`, ie accidentally modifiying the copy cannot happen, though `const auto` is something I see only rarely – 463035818_is_not_an_ai Dec 02 '20 at 09:15
  • @largest_prime_is_463035818 The goalposts moved ever so slightly - but I'll let my answer stand, for now at least. – Adrian Mole Dec 02 '20 at 09:16
1

I am not saying that this is the only correct approach, but it is a completely valid approach: Do not worry about performance when writing the code. Care about readability and keep performance for when you have something correct and working.

Taking this as premise my advice is the following:

Use this as default when you do not need to modify the containers elements:

for (const auto& e : v) {}

Only when for some reason you need a copy of the elements (but still do not want to modify the elements) use

void foo(int& x) {}
for (auto e : v) {
    e += 5;
    foo(e);
}

If you need to modify the elements use a reference:

 for (auto& e : v) {}

Last but not least, once you have working correct code and you realized that this loop is a bottneck by measuring: Look at the assembly to see what is better.

However, consider that any non-trivial loop body is likely to outweigh the difference between copying a small type and taking a reference.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

In theory, the object variable can potentially be more efficient because it doesn't involve indirection through a reference. However, at least in this simple example the reference version may be optimised to be identical with the object version, so it wouldn't necessarily matter in practice.

Wondering if these 2 things (passing and iterating by ref and value) might be somehow related.

Yes. Same rules of thumb apply to both cases. And in both cases it likely won't matter if everything is inlined by the optimiser.

eerorika
  • 232,697
  • 12
  • 197
  • 326