New C++23 range adaptors

The C++23 standard provides several new range adaptors in the ranges library. These include join_with, zip, and zip_transform. In this post, I will briefly show how these should work.

Note

Keep in mind though that no compiler supports these adaptors at this moment, so the code presented here in untested.

Joining

In C++20, there is an adaptor called join_view. This flattens a range of ranges of T into a range of T. Here is an example:

using namespace std::ranges;

auto l_as_string = []<typename R>(R range){
   return std::string(std::begin(range), std::end(range));
};

std::vector<std::string> words{"this", "is", "a", "demo"};
auto text = l_as_string(words | views::join);
std::cout << text << '\n';

The text variable contains the "thisisademo" string. Which is what you would need in various scenarios. But what if you actually need to include some separated between each range. For instance, what if you want the result to be "this is a demo"?

You ca solve this with join_with_view in C++23. The change is minimal, as show in the following snippet:

auto text = l_as_string(words | views::join_with(' '));

Zipping

Zipping is the process of talking one element at a time from two or more ranges and creating a new range of tuples from those elements.

Some people assume that zipping create a new range that intercalates elements, such as in the following example:

R1: {1, 2, 3, 4}
R2: {10, 20}
R3: {100, 200, 300}
zip(R1, R2, R3): {1,10,100,2,20,200}

This is incorrect. Zipping creates tuples as conceptually shown here:

R1: {1, 2, 3, 4}
R2: {10, 20}
R3: {100, 200, 300}
zip(R1, R2, R3): {(1,10,100),(2,20,200)}

This is possible in C++23 with the zip_view adaptor. Let’s take an example:

std::array<int, 4> n{ 1, 2, 3, 4 };
std::vector<std::string> w{ "one","two","three" };

auto z1 = views::zip(n, w) // { (1, "one"), (2, "two"), (3, "three") }

for(auto t : z1)
   std::cout << std::get<0>(t) << '-' 
             << std::get<1>(t) << '\n';

The result of zipping an array of integers and a vector of strings is a view of tuple<int&, string&>. We can use std::get to fetch the value of each element of the tuple.

Sometimes you might want to perform a transformation on the zipped values. For instance adding or concatenating them. In this case, the result is not a view of tuples, but a view of Ts. Here is a conceptual example where elements are added together producing a range of integers:

R1: {1, 2, 3, 4}
R2: {10, 20}
R3: {100, 200, 300}
zip_transform(+, R1, R2, R3): {111,222}

This operation is supported by the zip_transform_view adaptor in C++23. This is similar to zip_view except that its first parameter is a callable object (such as a function, a functor, or a lambda expression). Here is an example:

auto l_concat = [](int n, std::string const& s) {
   return std::to_string(n) + "-" + s;
};

auto z2 = views::zip_transform(l_concat, n, w); // {"1-one", "2-two", "3-three"}
for (auto e : z2) 
   std::cout << e << '\n';

References

P2321: zip

P2441: views::join_with

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.