Jonathan Boccara's blog

Introduction to the C++ Ranges Library

Published February 9, 2018 - 2 Comments

Announcement:

This Sunday I’ll be hostingĀ my first AMA, standing for Ask Me Anything, and I’d love for you to join in!

An AMA is an online event where you can ask any question to the host. And the AMA I’ll be hosting is about writing clear code (in particular in C++)! So I will take any question on the topic, and you can ask a question or vote for questions that other people have already submitted.

The event is on Sunday 8am-11am EST time. But you can start submitting your questions right now.

I’ll do my best to answer your questions, and hope to see you there on Sunday.

End of Announcement

 

Do you know the ranges library in C++?

This video will show what limitations of the STL it solves, and how it can make C++ code more expressive.

Since some of you expressed that they liked text more than videos, I’ve included a transcript of the video. I’d be glad to know if you find this useful, and if you’d like to have a transcript for other videos.

Transcript of the video:

Hello, this is Jonathan Boccara for Fluent C++!

Have you heard about the ranges in C++? It’s been a quite popular topic in the C++ community over the past few years, so let’s see what this is all about.

In C++, to manipulate collections we have the STL and its algorithms. They’re a fanstasic library but they have at least two issues.

The first one is that they force you to pass a begin and an end iterator to represent the collection, and the second one is that they are very hard to compose.

Let’s see how that looks like in code.

In this example I’m going to use wandbox.org because it has the ranges library in it. We’re going to write a piece of code that takes a collection of numbers and filters them on a predicate, say, filter on even numbers. And then apply a function on them like multiply by 2.

#include <algorithm>
#include <iostream>
#include <vector>

int main()
{
   std::vector<int> numbers = { 1, 2, 3 ,4, 5 };

   std::vector<int> evenNumbers;
   std::copy_if(begin(numbers), end(numbers), std::back_inserter(evenNumbers), [](int n){ return n % 2 == 0; });

   std::vector<int> results;
   std::transform(begin(evenNumbers), end(evenNumbers), std::back_inserter(results), [](int n){ return n * 2; });

   for (int n : results)
   {
      std::cout << n << ' ';
   }
}

So we start with the collection ‘numbers’ and then we send that to a copy_if algorithm. You know that in this algorithm I had to pass two iterators, begin and end, of numbers. That’s not really natural. I’d have prefered to pass numbers directly because that’s what we’re talking about. We don’t really care about iterators in such a simple use case. It copies the numbers that satisfy the predicate of being even and sends them out to ‘evenNumbers’ through the back_inserter.

In the second phase we send those results through the transform algorithms that applies the function ‘times2’ and sends that to ‘results’.

Let’s just run that code.

(outputs “4 8 “)

So 4 and 8 cause the even numbers are 2 and 4 and multiplied by 2 it’s 4 and 8.

The second issue that shows here is that it’s a lot of code to say not that much. See, you can’t compose algorithms the way you can compose functions. It has to have an intermediary result, and that’s a problem.

So, as you can see the STL makes it hard to compose algorithms and clutters the code with iterators when we actually mean to talk in terms of collections. Let’s see how the ranges solve those two problems.

What’s the ranges library exactly? It’s a library you can find on GitHub, on the GitHub of Eric Niebler who is its author. It’s a fairly large library that includes quite a lot of components and we’re going to see a couple of them.

Going back to our previous example, let’s see how we can fix the two problems of iterators showing all over the place and the difficulty of composing algorithms.

The concept behind the ranges library is the concept of being a range. Saying that somehting is a range is essentially saying that it can be iterated over, which means that it has a begin, it has an end and they both return something that behaves essentially like an iterator. It’s a vague definition but they’re are a lot of things to fit into that definition and one of the is the STL containers, like std::vector for example.

The ranges library allows quite a lot of things and one of them is plugin “view adaptrors” over ranges. Let’s just see an example.

auto evenNumbers = numbers | ranges::view::filter([](int n){ return n % 2 == 0; });

Here, I’ve created a this thing, which is the result of taking the range ‘numbers’ and plugging ranges::view::filter over it. This is a range view adaptor and it’s going to stick onto the ‘numbers’ range and it’s going to change the way it iterates.

More precisely, this expression is also a range which means that it has a begin and end and that you can iterate over it. Except that when you iterate over it it’s going to skip the numbers that don’t satisfiy the predicate. So here, ‘evenNumbers’ is a range which has only even numbers.

Actually, the whole thing is lazy. This is just a small component that’s a view over the range it’s plugged onto. When you iterate over this thing it’s actually going to ‘numbers’ every time but skips the numbers that don’t satify the predicate.

We can plug as many view adaptors as we like. For example let me plug a transform adaptor with a function that multiplies a number by 2.

auto evenNumbers = numbers | ranges::view::filter([](int n){ return n % 2 == 0; })
                           | ranges::view::transform([](int n) { return n * 2; });

All right, so this is now the result of my previous operation. It’s a ranges of numbers that have been filtered on the predicate and then have been applied a funciton. Let’s now see what’s inside this range.

(outputs “4 8 “)

And we have the same results as before, 4 and 8.

When you observe that code you don’t have any trace of iterators because we’re talking in terms of ranges, which are at a level of abstraction above iterators. They are implemented in terms iterators but they don’t show in this interface.

Also, you can see how easy it is to compose the equivalent of algorithms in the ranges library with just this operator|.

So in this regard the ranges solve the two problems with saw in the STL.

Now where can you expreriment with ranges? Well, one place we’ve just seen is in wandbox.org.

But you’ve got other popular websites that make ranges available. One of them is godbolt.org which a famous online compiler that lets you see the generated assembly code from a piece of C++ code. And there you can use ranges.

(visual tutorial on how to compile with ranges in godbolt.org)

Also, there is quick-bench.com, which is a quite popular website to perform micro-benchmarks and it has ranges as well. So you can experiment with them and see how they compare in terms of performance with other types of code.

There are actually two main things in this library. One of them is the concept of range to replace iterators. And this should be added in C++20, as I understand. And the second one is using these adpators and, as far as I know, they are not supposed to be in C++20.

I hope you enjoyed this video, talking about ranges. If you want more videos about writing expressive code in C++ you can subscribe to the channel. And if you liked it, put a thumb up!

Thanks, and I see you next time.

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed