Jonathan Boccara's blog

Implementing a Line Filter by Using C++ Ranges

Published April 3, 2020 - 0 Comments

In the last post we implemented a line filter by using standard C++14 features (with a little help of Boost), with the following interface:

auto const filteredText = join('\n', filter(contains(words), split('\n', text)));

We had to implement join, filter and split, which had the following interfaces:

std::string join(char delimiter, std::vector<std::string> const& inputs);

template<typename T, typename Predicate>
std::vector<std::string> filter(Predicate pred, std::vector<T> const& input);

std::vector<std::string> split(char delimiter, std::string const& text);

And contains takes a collection of words used to decide whether any given in text should be kept or left out of the resulting filteredText. It interface was this:

auto contains(std::vector<std::string> const& substrings);

It returns a lambda that implements the predicate.

An example where filtering lines is useful is when we want to see only the lines of a piece of source code that contain control flow keywords (if, elsefor, …) to reveal the structure of that piece of source code.

The task of filtering lines is a good example where using the ranges library leads to expressive code. To illustrate this, let’s modify our interface to make it benefit from ranges.

Implementation with ranges

We will use the range-v3 library, but all the components we will use should be standard in C++20. If you’re not familiar with ranges, you can check out the introduction to C++ ranges first.

Here is a possible implementation of our line filter by using ranges:

#include <range/v3/view/filter.hpp>
#include <range/v3/view/join.hpp>
#include <range/v3/view/split.hpp>

std::string const filteredText = text | ranges::view::split('\n')
                                      | ranges::view::filter(contains(controlFlowKeywords()))
                                      | ranges::view::join('\n');

The only thing we then need to implement is contains, the predicate we pass to ranges::view::filter. We had already implemented it in the previous post. As a reminder, here was its code:

bool contains(std::string const& string, std::vector<std::string> const& substrings)
{
    return std::any_of(begin(substrings), end(substrings),
              [string](std::string const& substring)
              {
                  return string.find(substring) != std::string::npos;
              });
}

auto contains(std::vector<std::string> const& substrings)
{
    return [&substrings](std::string const& string)
           {
               return contains(string, substrings);
           };
}

auto contains(std::vector<std::string> && substrings)
{
    return [substrings{std::move(substrings)}](std::string const& string)
           {
               return contains(string, substrings);
           };
}

And this is it: the other components (split, filter, join) are already provided by the library.

The interface has become nicer: it now reads in a natural order. First split, then filter, then join. This is different from our initial interface that relied on function calls only, and where the natural order could only be read from the inside out.

Notice that in the code using ranges we intialize filteredText, which is a std::string, with the result of the ranges operations. The type of the result of the ranges operations is not std::string, but it is convertible to a std::string. This allows to write the simple code above.

C++20

For the moment, to write this code we have to use on Eric Niebler’s range-v3 library. But in the (more or less distant) future, we’ll have this available directly in our production code.

Indeed, in the San Diego C++ committee meeting at the beginning of November 2018, ranges were integrated in the C++ standard draft. This means that, with the right namespace (std::, not ranges::) the above code should be standard in C++20!

This is really exciting news for the C++ community. The day were ranges will be in everyone’s code is getting closer.

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