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 T
s. 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';