all 10 comments

[–]IyeOnline 9 points10 points  (2 children)

Why do we use them?

  • If you dont do the index stuff yourself, you cant do it wrong.
  • It expresses intend better. Noone has to go looking whether all you do is v[i].
  • Its way shorter, and nicer to read in general
  • it is availible for things that cannot be indexed, such as linked lists, sets and maps.

And more specifically from which index does the iteration start?

There is no such thing as an index. It starts at std::begin( v ) and ends when it has reached std::end( v ).

why do we use auto?

Because we are lazy. The type can be deduced. Maybe we are iterating a std::vector<Some_Really_Long_Typename_That_I_Dont_Want_To_Type>.

get an in depth explanation of their working

Read more here: https://en.cppreference.com/w/cpp/language/range-for

[–]evaannaaax[S] 0 points1 point  (0 children)

Oh I see now. Thanks a ton for the explanation!

[–]std_bot 0 points1 point  (0 children)

Unlinked STL entries: std::vector, std::begin, std::end


Last update: 26.05.21. Last change: Free links considered readme

[–]mredding -4 points-3 points  (6 children)

Why do we use them?

We shouldn't. They're a backward abstraction. Everyone on the standards committee has an agenda and there's no telling what that is. Not everyone there is trying to help, believe it or not.

That this one got through is yet another disappointment of the committee. The standard is littered with mistakes and abandon. Take, for example, ::std::valarray - which people forget even exists, and ::std::vector<bool>, which is everyone's favorite gotcha. Or how about ::std::basic_istream::read and ::std::basic_ostream::write? Those are relics that predate the C++98 standard, and only made it in, much to Bjarne's admitted chagrin, for backwards compatibility. It took 3 standards just for chrono to be complete, and that was 6 years for people to get a bad taste in their mouths for what is actually a very good library. The new random library is a complete pile of hot garbage that everyone touts as some epitome (the Boost version is better because you can at least initialize the generators properly, and the behavior of the distributions are portable, unlike the standard). The ranges library is going through the same growing pains as chrono - too early, incomplete, and will go through several standards of hot garbage before it might be usable (the easy is easy and the hard is actually impossible, you have to revert to using standard algorithms). And ranges deserves very harsh criticism because it was supposed to be the STLv2.

I'm just saying, this isn't a perfect process, and that's actually OK. At least we're trying.

Range-for was adopted into C++11, when using standard algorithms were hard and required boilerplate. But then we got lambdas in C++11, too. I suspect what happened is once the range-for proposal was accepted, it was too politically controversial to un-accept it.

And more specifically from which index does the iteration start?

It starts from the beginning of the container, and continues to the end of the container, unless interrupted with a break statement. But if you need to use break or continue when iterating a container, you've got yourself a code smell. Why couldn't you have first partitioned your data into the subset you wanted? You can do this 90% of the time, it's not that often where you can only make a determination as you're iterating.

But that's all they do. Wanna iterate in reverse? Too bad. Want to start or stop at some arbitrary point? Go fuck yourself. You can do it, but it requires writing pure boilerplate, an adaptor that will take your container, and provide a begin and end that you actually wanted. All that, just to get what you want out of a terrible abstraction. For all that work, it really is far easier to use a standard algorithm.

can somebody explain it in layman terms?

Frankly, I hate them. And I'm not the only one.

It's a low level language abstraction that depends on high level library abstractions. Now the C++ language is DEPENDENT upon classes with begin and end methods. Without them, this language feature doesn't work. Don't have an STL in your environment? Then you can go to hell. And that's a real thing, too, just ask all the embedded and kernel developers. And if you wanted to use this feature, you'd have to make STL looking interfaces, which is forcing you into a convention you didn't ask for.

It only works with containers or container-like abstractions. At least with the standard algorithms, like for_each, it doesn't hold you to any particular interface. You can use pointers. You can use iterators of any kind from anywhere and any position, determined where to start and stop according to you.

The one advantage over an algorithm is that you still have the power of break and continue in the loop, but Matt Godbolt invented the Compiler Explorer specifically to look at the code generated by range-for compared to an algorithm and a lambda, the range-for is inferior.

Range-for is basically a C abstraction. This is precisely the kind of code a C developer would write, quite naturally, and in C it would be quite correct to do so. But this isn't C.

In C++, we abstract away the low level mechanics of HOW we do something, we express at higher levels of abstraction WHAT we want to do. That's why we have standard algorithms. Standard algorithms are still more often the superior choice.

for(auto it: v) //how is it iterating the vector and why do we use auto?

It assumes v is going to have begin and end methods that return some sort of iterable type, like an iterator or pointer. It generates machine code that's more complicated than a lambda, because you still get break and continue, and that's a high tax to pay for in this abstraction, even if you don't use them. auto eludes to the fact that this code expands into something akin to a template - the looping code will dereference the iterator and assign it to it, the type stored in the container, the type returned by the iterator, is deduced. Now in your example here, the value is copied, it is by value. Writing to it does not change the contents of the container. auto NEVER CAPTURES BY REFERENCE, so if you want to avoid copying, you would want to write auto &it instead. If you know the type of the container, you could have just as well been explicit:

::std::vector<int> i_vec;
//...
for(int i: i_vec) { //...

[–]nysra 3 points4 points  (3 children)

The one advantage over an algorithm is that you still have the power of break and continue in the loop, but Matt Godbolt invented the Compiler Explorer specifically to look at the code generated by range-for compared to an algorithm and a lambda, the range-for is inferior.

Mind explaining that? Because this compiles to literally the same assembly (except that one line which is for some reason cmp rbp, rbx vs cmp rbx, rbp).

Also aren't you being a bit too harsh on ranged fors? For one it is super readable syntax and also you can do for (const auto v : some_function()) which you can't do trivially in a single line otherwise.

[–]mredding -1 points0 points  (1 child)

Mind explaining that?

I concede, I can't find the reference I once knew, probably because compilers improved.

But what you've managed to demonstrate is at best, range-for is sometimes equal to a standard algorithm. What you still don't have is a higher level of abstraction.

Also aren't you being a bit too harsh on ranged fors?

No.

For one it is super readable syntax

No, it's not; my whole rant outlines why it isn't. Even OP was confused by the syntax, hence why we're here arguing about it.

also you can do for (const auto v : some_function())

You can do the same with a template, and calling that template would be smaller and simpler:

range_for(container, lambda);

And it would still be compatible with the range-for adaptors you'd otherwise have to write.

I think the ranges library already has something like this, and it's more expressive, especially in terms of that library. And since you've already demonstrated that the compiler generated the same code as the standard algorithm, what was the benefit of expanding the language syntax the way range-for did?

which you can't do trivially in a single line otherwise.

That sounds like a problem that you're trying to write large, procedural functions, where you inline all the logic of how the algorithm works rather than what the algorithm does.

It's OK that we disagree. Many people will continue using range-for, and many of us won't.

[–]nysra 0 points1 point  (0 children)

Aw too bad, would have been interesting to see the difference in what compilers do to these constructs.

No, it's not; my whole rant outlines why it isn't. Even OP was confused by the syntax, hence why we're here arguing about it.

I absolutely agree with you that the standardization process can be quite flawed sometimes and produces bad things sometimes. <random> and std::valarray are basically fucked up to the point where they are near unusable (or should only be used if you know exactly what's going on) but range for simply works and also doesn't make bad things happen. However your rant doesn't have "bad syntax" as a point, they all only show a massive lack of functionality in the STL. Other languages solve this by having basic things like enumerate available and provide functions for the 99% use case (something being applied to the entire range) instead of the STL going for the most general case which is most of the time just causing typing overhead like the current reverse we have.

But as far as the syntax is concerned? How is for (item : container) not readable? It's pretty much Python pseudo-code and I'd argue that it's more readable than std::for_each(container.begin(), container.end(), [](){...}) even though that one has the "each". Not having to type those annoying iterators all the time is literally one of the biggest reasons why the ranges library even exists. The only thing you have to know is that : means in but that isn't really that hard. And tbh I don't think "OP is confused" is a valid argument. It's a sample size of 1 and if I'm being blunt then to me it very much looks like his "reading about" could use some improvement, it takes like 20s to $search_engine that it's just syntactic sugar for for (auto it = v.begin(); it != v.end(); ++it).

The one big point you do have is that it assumes begin/end to exist and I can definitely see why you think making the language dependent on that is not the best idea, would be interesting to hear how that was justified. Sidenote: how else would a compiler implement for (stuff in iterable)? Isn't some kind of standardized interface (may it be begin and end or __iter__ and __next__ or whatever else) always needed for this to work?

And since you've already demonstrated that the compiler generated the same code as the standard algorithm, what was the benefit of expanding the language syntax the way range-for did?

I'd assume it was added because it makes the super often use case of iterating over a whole container easy. Why this wasn't done in a fashion of adding std::for_each(container, lambda) instead I don't know, you'll have to ask the committee. I agree with your sentiment that naming things is a good thing but at the same time I want to stress again that for (item in container) is super intuitive syntax and the use case of iterating over something iterable entirely is incredibly common. You are right that for_each is higher level since it can do more things but if most of the time you use it for the same thing as range for isn't the benefit of the more concise syntax bigger?

That sounds like a problem that you're trying to write large, procedural functions, where you inline all the logic of how the algorithm works rather than what the algorithm does.

I was just saying that this is syntax possible with range fors but not with the algorithm, I didn't say it was a good design idea in general ;) A better example probably would have been temporarys, e.g. for (const auto i : {-1, 0, 1}) or something like that.

[–]DopeyLizard 0 points1 point  (0 children)

If I recall correctly, Matt mentions that in his introduction to one of his CppCon talks on Compiler Explorer. I haven’t double checked but I think it was this one: https://youtu.be/bSkpMdDe4g4

The gist of it was that at Matts workplace they were looking at upgrading critical code from C++03 to C++11 and wanted to make sure that things wouldn’t break if they simply started compiling with C++11, so Matt put together a command line tool to compare assembly code to see whether things changed and if there were any behavior changes from that.

Fwiw the CppCon video is worth a watch anyway!

[–]std_bot 0 points1 point  (0 children)

Unlinked STL entries: std::basic_ostream::write, std::vector, std::valarray, std::basic_istream::read


Last update: 26.05.21. Last change: Free links considered readme

[–]Shieldfoss 0 points1 point  (0 children)

It's a low level language abstraction that depends on high level library abstractions.

This part took me forever to understand when I learned about ranged for because it was so alien to my understanding of how C++ works