Projection Functions

Learn how to use projection functions to apply range-based algorithms on derived data
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

When using an algorithm that acts on items in a collection, we often don't want the exact values in the collection to be the input.

For example, we might have a collection of numbers that we want to sort by their absolute value.

Or, we have a collection of Player objects, and we want to run an algorithm on a property of those objects, such as their email addresses.

Projection Functions

To generalize the above ideas, we want our algorithms to receive projections of the objects in our collection. Each projection is based on the original object but can have a different value and even a different type.

To support this, almost every range-based algorithm in the <ranges> library has an overload that receives a projection function.

These functions will receive objects within our collection as a parameter and will return a new object based on that parameter.

The algorithm will then use these projections to drive the behavior of the algorithm.

Example: Sorting Numbers By Absolute Value

In the previous lesson, we introduced the std::ranges::sort() algorithm. Its first argument is the range we want to sort, and the optional second argument is the comparison function to use for sorting the range.

In this example, we pass {} as the comparison function, causing the algorithm to use the default value:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Nums{-3, 5, 0};
  std::ranges::sort(Nums, {});
  for (auto Num : Nums) {
    std::cout << Num << ", ";
  }
}
-3, 0, 5,

The std::ranges::sort() function also has an optional third parameter, which is how we provide a projection function.

In the next example, we pass a function that projects the numbers in our collection to their absolute value, using std::abs():

#include <algorithm>
#include <iostream>
#include <vector>

int Project(int x) {
  return std::abs(x);
}

int main() {
  std::vector Nums{-3, 5, 0};
  std::ranges::sort(Nums, {}, Project);
  for (auto Num : Nums) {
    std::cout << Num << ", ";
  }
}

Now, our std::ranges::sort() call has sorted our numbers by their projection - that is, their absolute value:

0, -3, 5,

Why not just use the comparison function for this?

With the std::ranges::sort() algorithm, we could also have implemented this behavior by customizing the comparison functions:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Nums{-3, 5, 0};

  std::ranges::sort(Nums, [](int a, int b){
    return std::abs(a) < std::abs(b);
  });

  for (auto Num : Nums) {
    std::cout << Num << ", ";
  }
}
0, -3, 5,

But, the comparison function is specific to std::ranges::sort() - this is not a technique that we can always use.

Projection functions, meanwhile, are available to almost every algorithm in std::ranges.

Example: Projection to a Different Type

Our projection function does not need to return the same type of object that was contained in our original collection.

In this example, we sort Player objects by level, using a projection function:

#include <vector>
#include <iostream>
#include <algorithm>

struct Player {
  std::string Name;
  int Level;
};

int main() {
  std::vector Party {
    Player {"Legolas", 49},
    Player {"Gimli", 47},
    Player {"Gandalf", 53}
  };

  std::ranges::sort(Party, {}, [](Player& P) {
    return P.Level;
  });

  for (const auto& P : Party) {
    std::cout << "[" << P.Level << "] "
              << P.Name << "\n";
  }
}
[47] Gimli
[49] Legolas
[53] Gandalf

Here, we combine both a projection and a comparison function. The projection function will return an int, and then the comparison function will compare those int values:

#include <vector>
#include <iostream>
#include <algorithm>

struct Player {/*...*/} int main() {
std::vector Party {/*...*/} std::ranges::sort( Party, [](int a, int b) { return a > b; }, [](Player& P) { return P.Level; } );
for (const auto& P : Party) {/*...*/} }
[53] Gandalf
[49] Legolas
[47] Gimli

Example: Projection to a Class Member

Finally, we often want our algorithms to use a class member as their projection.

In such cases, we can simply pass a reference to that function. Below, we use Player::GetName as our projection function, causing our Player objects to be sorted alphabetically by name:

#include <vector>
#include <iostream>
#include <algorithm>

class Player {/*...*/} int main() { std::vector Party { Player{"Legolas"}, Player{"Gimli"}, Player{"Gandalf"} }; std::ranges::sort(Party, {}, &Player::GetName); for (const auto& P : Party) { std::cout << P.GetName() << '\n'; } }
[53] Gandalf
[47] Gimli
[49] Legolas

Summary

In this lesson, we've explored how to utilize projection functions with C++'s <ranges> library to manipulate and sort collections based on their inherent and derived properties. These techniques allow for more flexible and powerful data manipulation, enhancing the versatility of range-based algorithms.

Main Points Covered

  • Projection functions allow algorithms to operate on transformed or derived values from the objects in a collection.
  • The <ranges> library supports projection in almost all range-based algorithms, enabling operations on both value and type-transformed projections.
  • Examples demonstrated sorting numbers by their absolute value and sorting objects based on a member's value, showcasing the utility of projections.
  • We compared the use of projection functions with custom comparison functions, highlighting projections' broader applicability across different algorithms.
  • The lesson highlighted the syntax and usage of projection functions, including how to apply them to sort collections by a property or even by a member function's return value.

Was this lesson useful?

Next Lesson

Standard Library Views

Learn how to create and use views in C++ using examples from std::views
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 125 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Standard Library Views

Learn how to create and use views in C++ using examples from std::views
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved