Increased Complexity of C++20 Range Algorithms Declarations — Is It Worth?
In this article, learn more about the complexity of C++20 range algorithms declarations and determine if it's worth it.
Join the DZone community and get the full member experience.Join For Free
With the addition of Ranges and Concepts in C++20, our good old algorithm interfaces got super long "rangified" versions. For example,
copy is now 4 lines long... and it's just the declaration!
How to decipher such a long declaration? What benefits do we get instead? Is it worth it? Let's find out.
This article was originally published at bfilipek.com.
Super Long Declarations
Here are some algorithms that have the range versions in C++20. They are available in the
std::ranges namespace and located in the
And here's the standard version, just two lines:
Vs the "old" one:
You can see other algorithm in this handy page on C++ Reference: Constrained algorithms (since C++20) - cppreference.com and the "old" standard version at: Algorithms library - cppreference.com
Those new declarations might be intimidating at first, let's try to decipher that syntax.
As an example, we can take
std::ranges::copy_if which looks like a "monstrous template thing" at first!
Below you can find a simple use case:
See the live version @Wandbox
This code sample shows the super-easy client API that we can leverage. Just pass a whole container (no need for
begin/end) and the output sequence.
To decipher the declaration, we need to look at the four major parts:
- the return type
- the function declarator with a parameter list
One additional note:
ranges::copy_if is actually implemented not as a function... but a global function object... or ( see at stackoveflow). But that's a whole other story for now :)
The First Part:
The first part is the longest one:
It describes the input template parameters: the input range R, output O, the projection and also the predicate.
This may look a bit more complicated then the old
The main reason for its complexity is that the declaration uses Concepts which is a massive feature for C++20. For now, we can say that they add some extra meaning and requirements on the template types. The old interface takes almost everything (like a
void* in "template" meaning), and then we hope the compiler can compile the code... but with Concepts, we can specify some rules and so the compiler can spot mismatches early on.
For example, the input range has to satisfy the
input_range concept which is:
Makes sense... right?
The input range has to have
end() and also its iterator type has to be
Then the output is
weakly_incrementable so more or less it means that it can be incremented with
i++, like an output iterator.
The Second Part:
The next part is a simple template parameter for projection, by default, it's identity. In short thanks to projections, we can "see" elements obtained from the container differently. For example, we can iterate through the collection of "User" objects and extract only the name, or perform some additional computation. We'll touch on that later.
And there is also this long specification for the predicate:
Briefly, projection can perform addition operation on the input element and then the result is pushed into the predicate, which then decides if the element matches the copying criteria or not.
The Third Part:
The other part "
This time it restricts the input and output types so that they can read values from the input iterator and then write them into the output sequence. See the standard concept here: std::indirectly_copyable - cppreference.com
The Final Part:
After all of those restrictions, we can then read the most interesting part: the interface of the function:
Easy right? :)
What Do We Get Instead?
New versions of rangified algorithms are super large, and sometimes it's even hard to find the name of the function.
It's a great thing because we can now lament that C++ was super complicated and now it's getting even worse! :)
But Concepts and Ranges are not just for making our life more complex... it's actually the opposite.
What do we get instead? What are the advantages do we get paying the price of more extended interfaces?
We can just call the algorithm on the whole range, no need to ask for begin/end:
With the regular version of
std::copy you have to pass the start and end of the sequence:
That's a feature on its own and C++ developers dreamed about it for decades :)
Ranges allow us to compose algorithms together. You can add filters, views, transforms and many other operations which they return a new range. This is not possible with standard algorithms.
For example we can create a simple view and take the first four elements of our container:
See the live code @Wandbox
I mentioned this before, but now we can look at a simple example:
Live code @Wandbox
The range algorithms use
std::invoke to call the given projection on the given element of the range. Thanks to this approach, we can not only pass function objects but also ask for a data member of a class.
In our example above we can simply sort by
Package::price in just a single line of code. There even no need to pass custom comparators!
With Concepts, we get a longer, but more descriptive interface for template types. They are not only
<typename output, typename input> but you can now apply restrictions and convey that vital information through the code.
Compilers now have a way to check if the input argument for a template function matches the
requires clause and concepts in the declaration. They can potentially improve on the warning side and make their messages cleaner.
Reduced Compilation Time (hopefully)
It's improving! On one hand, Ranges are a complicated beast, and compiling that might make code bloat, but on the other hand, Concepts might help the compilers to process things faster.
Sorry for a little interruption in the flow :)
I've prepared a little bonus if you're interested in Modern C++, check it out here:
In this blog post, I wanted to present that while the new declarations of range functions and algorithms might look very complicated, they are here for a reason. Not only they give us better interfaces, with more precise parameters, but also they allow easy algorithm composition or even doing projections.
You have to learn new syntax and constructs, but it's worth the price.
It looks like that while you have 2x longer function declarations for those new algorithms, your final client code is several times shorter.
What do you think? Have you played with Ranges? What's your experience so far?
More from the Author:
Bartek recently published a book - "C++17 In Detail"- learn the new C++ Standard in an efficient and practical way. The book contains more than 360 pages filled with C++17 content!
Published at DZone with permission of Bartłomiej Filipek, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.