3

Whenever I iterate over a C++ array by pointer arithmetics (i.e. not with Java-style incrementing of an index), I first get the address of the last element in the array and then iterate as long as the current address is not the last address:

#include <iostream>
using namespace std;

int main()
{
    int values[] = { 1, 2, 3, 4, 5 };
    int* lastAddress = values + size(values);

    for (int *p=values; p != lastAddress; p++) {
        cout << "val: " << *p << endl;
    }
}

Is this the generally accepted way of doing this kind of thing?

TMOTTM
  • 3,286
  • 6
  • 32
  • 63
  • 2
    No, the "generally accepted" way is to use C++11's range iteration, to do all of this for you. This is the legacy way of working with arrays. C++11 is almost ten years old, and offers more convenient ways of doing many things of this nature. – Sam Varshavchik Sep 26 '20 at 13:27
  • 1
    Does this answer your question? [C++11 range based loop: How does it really work](https://stackoverflow.com/questions/33556196/c11-range-based-loop-how-does-it-really-work) – Aykhan Hagverdili Sep 26 '20 at 13:33
  • 1
    for (auto p = begin(values); p != end(values); ++p) , the for loop can be replaced like this too. – valarMorghulis Sep 26 '20 at 13:33
  • I would recommend `<` rather than `!=` as a general rule. – Den-Jason Sep 26 '20 at 13:43
  • I'm looking for an answer that keeps using pointers, that's why accept `std::begin` and `std::end`` approach. – TMOTTM Sep 26 '20 at 13:48
  • 1
    Guys, don't answer in the comments. Thanks. – Asteroids With Wings Sep 26 '20 at 13:49
  • 1
    @TMOTTM `std::begin` / `std::end` don't return pointers. They return *iterators*. – Jesper Juhl Sep 26 '20 at 13:51
  • 1
    @Den-Jason IMHO there are no advantages to using `<` rather than `!=`, but there are some extra failure scenarios. So my recommendation would be the opposite. See also https://stackoverflow.com/a/6673775/5910058 – Jesper Juhl Sep 26 '20 at 14:34
  • 1
    @JesperJuhl -- I agree that `!=` is the right choice for iterators. But `<` in ordinary iteration (e.g., over a range of numeric values) is a habit that many of us have developed; it can save things in more complex situations where, for example, the loop control variable might be incremented inside the loop. Using `<` avoids an off-by-one error that `!=` can fall into. So it's not totally without advantages. – Pete Becker Sep 26 '20 at 15:21
  • When you've worked with embedded systems and by some quirkiness a variable becomes corrupted, you learn to use `<` to cater for weird failure scenarios. But yes you need to be certain you're not comparing signed against unsigned ;) Thanks for the link Jesper – Den-Jason Sep 26 '20 at 16:14
  • @Den-Jason "by some quirkiness a variable becomes corrupted, you learn to use < to cater for weird failure scenarios" - In that situation, the solution is *not* to try to carry on with the program in a broken state. The correct solution is to crash as soon as possible and then use the crash dump to *fix* the "quirkiness". – Jesper Juhl Sep 26 '20 at 17:43
  • @JesperJuhl that's fine for desktop software, not fine for e.g. a flight control or engine management system, which would need to indicate a fault flag and continue with a failure-back-up mode. I've experienced SRAM failures before, where a not-equals check in a loop would have stalled the system for several hours. Also, a faulty not-equals check to exit a loop does not indicate a fault, it simply stalls. A less than condition, followed by a check for the expected terminal value, does. – Den-Jason Sep 26 '20 at 17:46
  • @Den-Jason Are you saying that you are fine with a plane continuing operations with its engine control program being in some undefined/invalid state? – Jesper Juhl Sep 26 '20 at 17:50
  • @JesperJuhl I am fine with any system that can cope with failure without stopping with "computer says no". That's why there are failure-back-up-modes in safety critical systems. – Den-Jason Sep 26 '20 at 17:51

2 Answers2

5

You can use a range based for loop. Like so:

for (const auto val : values) {
    std::cout << "Val: " << val << '\n';
}

Note the use of \n rather than std::endl. std::endl implies a std::flush of the stream and you probably don't need that after every line. You can do one after the loop if needed.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
  • Note: this assumes that `values` is `std::array` or `std::vector`, not a simple C array as in question. But it is in general not a good idea to use simple C arrays in C++. – Wolfram Sep 26 '20 at 13:30
  • 2
    @Wolfram works for C arrays too. https://godbolt.org/z/rq3jxb – Aykhan Hagverdili Sep 26 '20 at 13:30
  • 2
    @Wolfram Nope. Native arrays are fine. – Deduplicator Sep 26 '20 at 13:31
  • 1
    Better improve and maybe drop an explanatory note. As you did for the abominable using namespace, though [dropping a note would be nice](/questions/1452721/why-is-using-namespace-std-considered-bad-practice). – Deduplicator Sep 26 '20 at 13:32
  • Native arrays are "fine", but `std::array` is less prone to mistakes and with them you can do everything you can do with native arrays. – Wolfram Sep 26 '20 at 13:34
  • @Wolfram `std::array` misses sizing from the initializer, or declaring an extern one without knowing the size. Also, it needs an extra-include. Still, being first-class and allowing zero-size can be important too. – Deduplicator Sep 26 '20 at 13:38
  • @Deduplicator in C++17 this is fine `std::array a = { 1, 2, 3 };` due to the addition of deduction guides. – Jesper Juhl Sep 26 '20 at 13:40
  • 2
    @Wolfram While it's true that `std::array` can often be preferred to C arrays, that has nothing to do with anything in the question, for which ranged-based for, `std::begin` and `std::end` are all perfectly fine as-is. The dimension is part of the type, and that's how they work. :) – Asteroids With Wings Sep 26 '20 at 13:47
4

Is this the generally accepted way of doing this kind of thing?

No, it isn't. You shouldn't do hand made pointer arithmetics for determining start and end of a container. That's what std::begin() and std::end() are for:

#include <iostream>
using namespace std;
int main() {
    int values[] = { 1, 2, 3, 4, 5 };

    for (auto p = std::begin(values); p != std::end(values); p++) {
        cout << "val: " << *p << endl;
    }
}

As a short form you can use a range based for loop, which uses the same mechanism under the hood:

for(auto value : values) {
    cout << "val: " << value << endl;
};

Please note, that this only works with arrays declared locally with a well known size (sizeof()).

If you get such array definitions passed to a function, you still also need to pass the size:

 foo(int values[], size_t size) {
      for(auto val = std::begin(values); p != std::begin(values) + size; ++p) {
          // ...
      }
 }

The standard accepted way is to ditch raw arrays at all, in favor of using std::array<T,N> for arrays of known size, or std::vector<T> for arrays of unknown size.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190