1

In some code snippet, here on stackoverflow mostly, I often found myself reading code like this:

std::array<std::string_view, 3> appleNames{"Fuji", "Golden", "Gala"};
std::copy (appleNames.begin (), appleNames.end (), std::ostream_iterator<std::string_view> (std::cout, "\n")); // print all the names

It's just me or this is not so glad to see? Isn't this version more understandable at a first sight?

std::array<std::string_view, 3> appleNames{"Fuji", "Golden", "Gala"};
for(const auto& name : appleNames)
    std::cout<<name<<"\n"; // print all the names

I know this question wiil be mostly opinion based, but I would like to understand what are the benefits with the first version, readability wise mainly, 'cause I can't really figure it out.

Federico
  • 743
  • 9
  • 22
  • 1
    you are doing two different things. there is `for_each()` in the std lib. Algorithms are tools. And you are free to use them if you need them. – Raildex Jul 22 '21 at 07:45
  • You know that this is opinion based, and you already have your opinion. Are we knocking in open doors here? – super Jul 22 '21 at 08:32
  • I think you should change your question title into: "best ways of printing a sequence" and then explain what you mean by best. Or change the example into something more complex. – Koronis Neilos Jul 22 '21 at 08:53

4 Answers4

3

To understand the motto "algorithms are more readable" you need to consider a bit of history.

Suppose bare index based loops are out, then in C++98 the comparison is between this (no string_view, no std::array):

#include <vector>
#include <string>
#include <iostream>
#include <iterator>

int main() {
    std::vector<std::string> appleNames;
    appleNames.push_back("Fuji");
    appleNames.push_back("Golden");
    appleNames.push_back("Gala");
    std::copy (appleNames.begin (), appleNames.end (), std::ostream_iterator<std::string> (std::cout, "\n")); // print all the names
}

And this:

#include <vector>
#include <string>
#include <iostream>
#include <iterator>

int main() {
    std::vector<std::string> appleNames;
    appleNames.push_back("Fuji");
    appleNames.push_back("Golden");
    appleNames.push_back("Gala");

    for (std::vector<std::string>::iterator it = appleNames.begin(); it != appleNames.end(); ++it){
        std::cout << *it;
    }
}

It was common to have for loops that span more than the screen width, just to get the advantage of iterators. One had to write lots of code, just to say "I want to iterate this range with iterators". auto and range based for loop make all this much easier and the difference is less drastic now. Things change over time. For example writing "flat code" was a useful aim. Having no intendation could be achieved by using algorithms instead of loops. On the other hand, now we use lambdas, because we can, and the difference between algorithms and loops is much less. Especially for_each is largely obsolete since range based loops do exist.

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

Algorithm better expresses in general intent with naming, contrary to for-loops.

I think that std::ostream_iterator is not known enough though to be used, and copy meaning is discutable.

Verbosity is also a criteria for readability.

Ranges-version of algorithms are less verbose than non-ranges versions,
as for-range is less verbose than old for-loop.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
2

Printing contents is moderately good example here, because const auto& in the for loop states explicitly nothing changes in the collection. However

  • some people tend to ignore const-correctness/use plain C-style loops; these are just noise.
  • Imagine the following example
for(auto i=apples.begin(), e=apples.end(); i!=e; ++i) {
  if(*i == "Gala") 
    break;
}

vs

auto found = std::find(begin(apples), end(apples), "Gala");

Which one do you find more readable at the first sight? For me find wins, because:

  • intent is explicit. Once find is visible, one doesn't have to check on iterator constness.
  • no nesting, code is more "left-aligned".

Thus, I mostly agree with aforementioned Sean Parent's lecture. The main problems I see here are

  • People are taught C with classes instead of C++
  • C++ (especially the old one) is not nice when it comes to code locality. This changes vastly with C++11 and possibility to use lambdas.
  • Some algorithms' names are not very clear. But getting to know them comes with experience...
Jarod42
  • 203,559
  • 14
  • 181
  • 302
alagner
  • 3,448
  • 1
  • 13
  • 25
1

Disclaimer I wrote most of the code from the first example. code example 1

The reason why I did not use a for each is because a for loop is too mighty. Which leads to problems in reasoning because it is hard to figure out the intend if you use mighty tools. Try to use a more limited tool and the reader has a higher chance to find out what you try to do.

For more Information please watch the legendary talk from Sean Parent No raw loops talk from Sean Parent.

Koronis Neilos
  • 700
  • 2
  • 6
  • 20
  • 2
    you forgot about one thing: `std::for_each(std::execution::par,...)` . same with algo's . And you can't use it in `for()` . you need each run isolated to do so – Алексей Неудачин Jul 22 '21 at 08:10
  • and there're things you can't do in `for_each` also. – Алексей Неудачин Jul 22 '21 at 08:21
  • Still `std::copy (appleNames.begin (), appleNames.end (), std::ostream_iterator (std::cout, "\n"));` is horrible and hard to read. The intention is absolutely not clear on first sight. And `for(const auto& apple : apples) std::cout << apple << "\n;` is hard to do wrong, so I don't really see ANY benefit in using your approach. – Stefan Riedel Jul 22 '21 at 08:27
  • @StefanRiedel in fact that's exactly why I posted this question. I'm aware of various benefits in specific cases (like the one in the talk, where 20 lines become 5 and more readable), I was focused on examples like this one spcifically – Federico Jul 22 '21 at 08:29
  • 1
    It is fine @Federico – Koronis Neilos Jul 22 '21 at 08:29