Projections with Ranges

Contents[Show]

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:

 TimelineCpp20

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 = {});

 

When you study the first overload, you notice that it takes a sortable range 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 that was added with C++20 is a function object defined in the header <functional>.
 
In short, here are the components:
 
  • Comparators: Comp (binary functions that returns a boolean)
  • Projections: Proj (mapping of a set into a subset)
  • Sentinel: std::sentinel_for<I> (a special value that indicates  the end of a sequence)
  • Concepts: std::random_access_iterator, std::sortable<I, Comp, Proj>, and std::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

The algorithms of the ranges library operate directly on the container. This is due to the fact, that the projection is by default std::identity.
 
In the following example, I apply a projection to the data type 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.

rangeProjection

When your projection is more demanding, you can use a callable such as a lambda expression.

// 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 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 the one in line (4). I want to emphasize this. Line (3) uses a projection as sorting criteria, but line (4) is a parametrized std::ranges::sort with a binary predicate, given by the lambda expression.
 
 
rangeProjectionCallable
 
Most ranges algorithms support projections.

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, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Evangelos Denaxas, 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, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Ralf Holly, Juan Dent, George Liao, Daniel Ceperley, and Jon T Hess.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, Ralf Abramowitsch, John Nebel, and Mipko.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

Mentoring Program in English

Do you want to stay informed about my mentoring programs? Write to This email address is being protected from spambots. You need JavaScript enabled to view it..

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmSmall

 
 
 

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 793

Yesterday 6750

Week 15130

Month 235966

All 9709598

Currently are 156 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments