Hello, and welcome to C++ Weekly. I'm your host,
Jason Turner. I'm available for code reviews, contracting and on-site training.
On this episode I'm going to give you a bit of a quick start into standard Ranges with a little bit
of the why and a little bit of the how, and just for the record this is not going to be very deep.
All right.
So the first thing that I want to make you aware of is that they
just give us this handy tiny little adapter for our common algorithms.
Now let's just say we have this source of data and we want to do something with it, like,
I don't know, we just want to see if all of these things are less than five or something like that.
Now I'm just going to go ahead and give you a second to digest this code and see what
you think of it. There is a bug here, and it's unfortunately kind of a common bug,
and as we can see our compiler is not warning us about it,
so let's just go ahead and see if we can get some sort of a runtime error.
Interestingly, we are not getting a runtime error. Let's go ahead and turn off the optimizer
and then see what happens. We're still not getting a runtime error - this is spectacular.
Okay, so you may have noticed first of all that GCC was returning the wrong answer,
and now Clang is returning "the correct answer," but we are still not getting a runtime error.
Okay, well there we go. We actually had to enable GLIBC's debug iterator support
to actually get a runtime error here. This is absolutely ridiculous, but here we are. So the
problem was I'm getting a temporary back from this function and then I was calling
'begin' on the first temporary returned and I was calling 'end' on the second temporary returned,
so these should have been a mismatched iterator pair; but coincidentally they happen to be things
that were allocated near each other in memory and we didn't get any kind of runtime error. So
yeah, that's exciting, but if you've encountered something like this before
you end up doing something like this. Well, that is, if you don't want a runtime error,
an undefined behavior, in your program; but you know, what can you ask for?
Okay, so this works properly. Now the point here is that Ranges actually gives us a handy little
helper that we can do like this and we no longer have to pass in this explicit begin
and iterator pair, this just works, and that's really cool. So that's the first
thing that we get. Now the second thing is just this handy little pipeable range views
and you saw some of this if you watched my episode on "Finally! A Simple String Split."
Now in this case we can take a range and make a lazy evaluated view of it surprisingly easily.
Now unfortunately Clang doesn't yet have full support for these things so I needed to move over
to GCC trunk, but as we can see I was able to drop 1 element and take the next 2. So I dropped the 2,
I took the 3 and the 4, and then 3 and 4 are both less than 5 and the program returns 1
from main. If I were to update this to say "drop 2 and then take the next 2," then I
would be getting the elements 4 and 5. 5 is not less than 5, so we should get 0 returned
from main. There we go. So handy cool little adaptable composable pipeable views of things.
Now this is kind of my most favorite application, and it comes down to just this very annoying
little aspect, like, just say you want to do a ranged for-loop over all but the first
element of something, and you know, it may be, perhaps, it seems a little bit silly,
but it's a problem that comes up during data processing more often than I would like for it to.
Yes, now before you comment, I know that I could use a for-each algorithm on this,
but I know a lot of people don't like that for simple situations and in this case, uh,
the argument still applies, however you choose to actually iterate over the data.
So just a very simple way of skipping the first element in a range of data. To me this
is incredibly helpful, and it doesn't have to be the result of a function. It could be something
else that lives here already in the stack, and again, data processing things - that's often
that you want to skip just the first element and it's always been harder than it needed to
be up to this point. Now the fourth thing that I want to call out is this lazy evaluation has,
again, interesting implications with something like the std::transform_view,
and this is something that I didn't really anticipate finding but, again,
this gives us a simple solution to a relatively annoying problem in C++.
Now I'm going to do a quick walk through of this just to explain what it's doing,
but basically what I've done is I've created this little lambda. This is my transformer,
and it takes a single element. It is itself mutable, and it has this, which is its current
index; and so each time this function is called I return a std::pair that is of the first element,
being a size_t, the second element, being a const reference to the thing that was passed
in using C++20's explicit template parameters for lambdas here because I really wanted that type,
and then I instantiate this thing with the post incremented index (which is intentional)
and the element via reference here. So then I have my ranged for-loop and I take in a const
reference structured binding of the index and the element, and I take get_data and I pipe it
through this transformer that is a make_index, and then I print out the elements; and as you can see,
I have 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 printed with the 10 elements that were in my view here.
So I could, with no problem, do the similar kinds of things that we've already been playing with
and just say, "Well, I just want to print out the first 5 elements,"
and these things are all lazily evaluated and as far as I can tell pretty light weight for
compile time code being generated. Again, go back to the previous episode on a "Simple String Split"
if you would like to see more about that. So those are the main four things that I wanted
to cover about Ranges, hopefully just to wet your appetite a little bit. Just a couple of
quick notes - they're all constexpr capable, which again hints to the idea that we might be
able to do something like constexpr scripting engine, something that's been in the back of my
mind for the last five years or so but I haven't really had the time to execute that yet. Also,
if you want this straight away and your compiler doesn't currently support it or your library,
standard library vendor, doesn't support this you can get Range V3 or (also) you
should be aware that C++23 has a ton more Ranges capability being added to it, but again this is
the quick start to Ranges. I hope this got you a little bit interested in looking more into this,
and thank you for watching this episode of C++ Weekly. Be sure to subscribe. Give
this a thumbs up, and consider becoming a patron, or a supporter of the channel,
a member of this channel, or buying one of my books that's in the video description here.