Projections with Ranges
The algorithms of the ranges library are lazy, can work directly on the container, and can easily be composed. But they have more to offer: projections. A projection is a mapping of a set into a subset. Let me show you in this post what that means:
I ended my last post, “The Ranges Libary in C++20: More Details” with a comparison of std::sort
and std::ranges::sort
. Here are the two overloads of std::ranges::sort
:
template <std::random_access_iterator I, std::sentinel_for<I> S, class Comp = ranges::less, class Proj = std::identity> requires std::sortable<I, Comp, Proj> constexpr I sort(I first, S last, Comp comp = {}, Proj proj = {}); template <ranges::random_access_range R, class Comp = ranges::less, class Proj = std::identity> requires std::sortable<ranges::iterator_t<R>, Comp, Proj> constexpr ranges::borrowed_iterator_t<R> sort(R&& r, Comp comp = {}, Proj proj = {});
R
, a predicate Comp
, and a projection Proj
. The predicate Comp
uses for default ranges::less, and the projection Proj
the identity std::identity that does return its arguments unchanged. std::identity
have been added with C++20, a function object defined in the header <functional>.- Comparators:
Comp
(binary functions that return a boolean)
- Projections:
Proj
(mapping of a set into a subset)
- Sentinel:
std::sentinel_for<I>
(a unique value that indicates the end of a sequence)
- Concepts:
std::random_access_iterator, std::sortable<I, Comp, Proj>
, andstd::sentinel_for<I>
In contrast, the second overload does not return an Iterator I, but a ranges::borrowed_iterator_t
. Of course, this is also a concept and guarantees that the returned iterator is safe to use afterward. Consequentially, we call this iterator a safe iterator. I will write more about std::ranges::borrowed_iterator_t
in an upcoming post.
A projection is a mapping of a set into a subset. What does this mean?
Projection
PhoneBookEntry
.// rangeProjection.cpp #include <algorithm> #include <functional> #include <iostream> #include <vector> struct PhoneBookEntry{ // (1) std::string name; int number; }; void printPhoneBook(const std::vector<PhoneBookEntry>& phoneBook) { for (const auto& entry: phoneBook) std::cout << "(" << entry.name << ", " << entry.number << ")"; std::cout << "\n\n"; } int main() { std::cout << '\n'; std::vector<PhoneBookEntry> phoneBook{ {"Brown", 111}, {"Smith", 444}, {"Grimm", 666}, {"Butcher", 222}, {"Taylor", 555}, {"Wilson", 333} }; std::ranges::sort(phoneBook, {}, &PhoneBookEntry::name); // ascending by name (2) printPhoneBook(phoneBook); std::ranges::sort(phoneBook, std::ranges::greater() , &PhoneBookEntry::name); // descending by name (3) printPhoneBook(phoneBook); std::ranges::sort(phoneBook, {}, &PhoneBookEntry::number); // ascending by number (4) printPhoneBook(phoneBook); std::ranges::sort(phoneBook, std::ranges::greater(), &PhoneBookEntry::number); // descending by number (5) printPhoneBook(phoneBook); }
phoneBook
(line 1) has structs of type PhoneBookEntry
(line 1). A PhoneBookEntry
consists of a name
and a number
. Thanks to projections, the phoneBook
can be sorted in ascending order by name
(line 2), descending order by name
(line 3), ascending order by number
(line 4), and descending order by number
(line 5). The empty curly braces in the expression std::ranges::sort(phoneBook, {}, &PhoneBookEntry::name)
cause the default construction of the sorting criteria that is, in this case, std::less
.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
You can use a callable such as a lambda expression when your projection is more demanding.
// rangeProjectionCallable.cpp #include <algorithm> #include <functional> #include <iostream> #include <vector> struct PhoneBookEntry{ std::string name; int number; }; void printPhoneBook(const std::vector<PhoneBookEntry>& phoneBook) { for (const auto& entry: phoneBook) std::cout << "(" << entry.name << ", " << entry.number << ")"; std::cout << "\n\n"; } int main() { std::cout << '\n'; std::vector<PhoneBookEntry> phoneBook{ {"Brown", 111}, {"Smith", 444}, {"Grimm", 666}, {"Butcher", 222}, {"Taylor", 555}, {"Wilson", 333} }; std::ranges::sort(phoneBook, {}, &PhoneBookEntry::name); // (1) printPhoneBook(phoneBook); std::ranges::sort(phoneBook, {}, [](auto p){ return p.name; } ); // (2) printPhoneBook(phoneBook); std::ranges::sort(phoneBook, {}, [](auto p) { // (3) return std::to_string(p.number) + p.name; }); printPhoneBook(phoneBook); std::ranges::sort(phoneBook, [](auto p, auto p2) { // (4) return std::to_string(p.number) + p.name < std::to_string(p2.number) + p2.name; }); printPhoneBook(phoneBook); }
std::ranges::sort
in line (1) uses the attribute PhoneBookEntry::name
as a projection. Line (2) shows the equivalent lambda expression [](auto p){ return p.name; }
as projection. The projection in line (3) is more demanding. It uses the stringified number concatenated with the p.name.
Of course, you can use the concatenated stringified number and the name directly as sorting criteria. In this case, the algorithm call in line (3) is easier to read than in line (4). I want to emphasize this. Line (3) uses a projection as a sorting criterion, but line (4) is parametrized std::ranges::sort
with a binary predicate given by the lambda expression.What’s next?
In my next post, I will write about sentinels. They specify the end of a range and can be regarded as generalized end iterators.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!