peltorator's blog

By peltorator, 5 months ago, In English

I decided to learn some C++20, and now I am trying to incorporate for loops with std::ranges or std::views stuff. I stumbled upon the fact that there are two options: ranges::iota_view and views::iota. They seem equivalent to me, but I wonder whether I am missing something. On the internet, I found a mention of ranges::iota_view being better than views::iota "in terms of performance", but I was not able to reproduce it. On the other hand, views::iota seems shorter and easier to alias: one can just write const auto &range = views::iota; and use Python-like for loops, but const auto &range = ranges::iota_view; wouldn't compile. One could instead use using range = ranges::iota_view<int, int>;, but this now works only for integers (adding template<typename T> to using would force me to always write range<int, int>() because apparently using in C++ does not support argument deduction). I would like to hear your opinion on this. Has anybody thought about the same question before? What did you do?

P.S. I am asking about purely competitive programming usage, not software engineering.

  • Vote: I like it
  • +11
  • Vote: I do not like it

»
5 months ago, # |
  Vote: I like it +22 Vote: I do not like it

It's interesting that you mention ranges::iota_view being better than views::iota in terms of performance, because it makes more sense for it to be the opposite. The person who claimed this might be compiling without any optimizations turned on (even O2). Your observation is more plausible — that's why such claims should be backed with proper measurements.

The general intent behind having both of these APIs exposed in the STL is:

  • Anything that looks like std::views::* is a user-facing algorithm (in fact, a customization point object, which behaves like a lambda). It can make smarter choices than to naively apply your ranges algorithm to the input, and when it comes to the worst case, it will be at least as fast as its std::ranges::*_view counterpart. For example, when you reverse a view twice, std::views::reverse generates two fewer levels of nesting/wrapping than std::ranges::reverse_view, which might put more stress on the compiler than necessary. Even the return types of the views and the ranges counterparts are different — though you could do the same stuff semantically, it becomes easier for the compiler to optimize it away.
  • Anything that looks like std::ranges::*_view is an implementation detail. The only reason you should be using it is when you want to implement a view (the corresponding view or some other related view). It really is something that only library implementers should care about, and if you are okay with using it as a replacement for std::views::*, you should better move to using the std::views variant. std::ranges::*_view is a class, and it binds you to the "old" type system more tightly, which might be a good thing for implementers (not saying that it is), but not for the compiler and users.

The aliasing thing is a nice touch that comes from the fact that it is a customization point objects, and can be thought of as a lambda that you can pass around.