Projections and Algorithms

Learn how to use projections with standard library algorithms in C++
This lesson is part of the course:

Professional C++

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

168.jpg
Ryan McCombe
Ryan McCombe
Posted

When we’re using an algorithm that acts on items in a collection, often we will want to apply some temporary transformation to each item first.

For example, we might have a collection of numbers, and we want to run an algorithm on their absolute values.

Or, we might have a collection of Character objects, and we want to run an algorithm on a property of those characters, such as their name.

Scenarios like these can be elegantly solved with projection, and most of the standard library algorithms have a parameter that lets us set it up.

Example: Sorting Numbers By Absolute Value

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

std::vector Nums { -3, 0, 5, };
std::ranges::sort(Nums, std::ranges::greater{});

The std::ranges::sort function also has an optional third parameter, which allows us to pass a projection function. In the next example, let’s pass a lambda there, to make our algorithm run on a projection instead.

In this case, we are using the default argument for the second paremter, and using the third parameter to project the integers to their absolute value, using std::abs:

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

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

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

  for (const auto& Num : Nums) {
    std::cout << Num << ", ";
  }
}

The values are not changed, but they were sorted based on their projection instead of their real value:

0, -3, 5,

Why not just use the comparison function for this?

With the sort algorithm, projections are generally unnecessary, as we can meet our needs using appropriate comparison functions:

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

But, not all algorithms accept comparison functions, because not all algorithms require comparisons.

Almost all range based algorithms support projection, so it's a more widely available technique, and a useful tool to be aware of.

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 Character objects by level, using a projection function:

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

class Character {
public:
  int Level;
  std::string Name;
};

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

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

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

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

std::ranges::sort(
  Party,
  [](int a, int b) { return a > b; },
  [](Character& Character) { return Character.Level; }
);
[53] Gandalf
[49] Legolas
[47] Gimli

Projection to an Object Property

Finally, the value we want to project to will often be returned from a getter, defined within our objects’ class. Where that is the case, we can simply pass a reference to that function:

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

class Character {
public:
  Character(std::string Name, int Level) :
    Name(std::move(Name)),
    Level(Level) {};

  std::string GetName() const { return Name; }
  int GetLevel() const { return Level; }

private:
  int Level;
  std::string Name;
};

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

  // Sort characters in alphabetical order by name
  std::ranges::sort(Party, {}, &Character::GetName);

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

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Professional C++

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

7a.jpg
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:

  • 106 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
DreamShaper_v7_fantasy_female_space_pirate_Sidecut_hair_black_1.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved