Back to TILs

C++ Ranges — Part 5 — Projections

Date: 2023-03-12Last modified: 2023-03-22

Table of contents

// Point.hpp
#ifndef POINT_H
#define POINT_H
#include <compare>
#include <iostream>

class Point {
  friend std::ostream& operator<<(std::ostream& out, const Point& p);

 public:
  Point() = default;
  Point(double x, double y) : m_x(x), m_y(y) {}
  // Operators
  bool operator==(const Point& other) const;
  std::partial_ordering operator<=>(const Point& right) const;

 private:
  double length() const;  // Function to calculate distance from the point(0,0)

 public:
  double m_x{};
  double m_y{};
};

inline std::ostream& operator<<(std::ostream& out, const Point& p) {
  out << "\nPoint [ x : " << p.m_x << ", y : " << p.m_y
      << " , length : " << p.length() << " ]";
  return out;
}

#endif  // POINT_H
// Point.cpp
#include <cmath>
// #include "point.h"

double Point::length() const {
  return sqrt(pow(m_x - 0, 2) + pow(m_y - 0, 2) * 1.0);
}

bool Point::operator==(const Point& other) const {
  return (this->length() == other.length());
}

std::partial_ordering Point::operator<=>(const Point& right) const {
  if (length() > right.length()) {
    return std::partial_ordering::greater;
  } else if (length() == right.length()) {
    return std::partial_ordering::equivalent;
  } else if (length() < right.length()) {
    return std::partial_ordering::less;
  } else {
    return std::partial_ordering::unordered;
  }
}
template <typename T>
void print_collection(const auto label, const T& collection) {
  std::cout << "\n" << std::setw(30) << label << " [";
  for (const auto& element : collection) {
    std::cout << " " << element;
  }
  std::cout << "]" << std::endl;
}
  // Projections : usually the sorting is done based on operator<
  // but you get one chance to write operator <
  // sometimes you want to sort things based on another scheme or
  // member variable other than the one used by operator<
  // You can do that with projections. For example, sorting based on y
  // for Point can be achieved with a y projection as shown in this example

  std::cout << std::endl;
  std::vector<Point> points{{10, 90}, {30, 70}, {20, 80}};
  print_collection("Initial", points);

  // Sorting with the default comparator
  std::ranges::sort(points, std::less<>{});  // Default sort based on distance
  print_collection("Sort std::less<>{} <=> length", points);

  // Sorting with a projection: The data is passed into the projection before
  // it's passed into the comparator.
  // std::less<> is going to compare two doubles instead of comparing two
  // Points.
  std::ranges::sort(points, std::less<>{}, [](auto const& p) { return p.m_x; });
  print_collection("Sort projection on Point::m_x", points);

  std::ranges::sort(points, std::less<>{}, [](auto const& p) { return p.m_y; });
  print_collection("Sort projection on Point::m_y", points);

  // Projection on Point::m_y with direct member variables"
  std::ranges::sort(points, std::less<>{}, &Point::m_x);
  print_collection("Projection direct on member variable m_x", points);
  // Projections with for_each
  std::cout << "Projections with for_each: " << std::endl;

  auto print = [](const auto& n) { std::cout << " " << n; };
  using pair = std::pair<int, std::string>;
  std::vector<pair> pairs{{1, "one"}, {2, "two"}, {3, "tree"}};

  std::cout << "project the pair::first: ";
  std::ranges::for_each(pairs, print, [](const pair& p) { return p.first; });
  std::cout << std::endl;

  std::cout << "project the pair::first: ";
  std::ranges::for_each(pairs, print, &pair::first);
  std::cout << std::endl;

  std::cout << "project the pair::second: ";
  std::ranges::for_each(pairs, print, [](const pair& p) { return p.second; });
  std::cout << std::endl;

Possible output



                       Initial [ 
Point [ x : 10, y : 90 , length : 90.5539 ] 
Point [ x : 30, y : 70 , length : 76.1577 ] 
Point [ x : 20, y : 80 , length : 82.4621 ]]

 Sort std::less<>{} <=> length [ 
Point [ x : 30, y : 70 , length : 76.1577 ] 
Point [ x : 20, y : 80 , length : 82.4621 ] 
Point [ x : 10, y : 90 , length : 90.5539 ]]

 Sort projection on Point::m_x [ 
Point [ x : 10, y : 90 , length : 90.5539 ] 
Point [ x : 20, y : 80 , length : 82.4621 ] 
Point [ x : 30, y : 70 , length : 76.1577 ]]

 Sort projection on Point::m_y [ 
Point [ x : 30, y : 70 , length : 76.1577 ] 
Point [ x : 20, y : 80 , length : 82.4621 ] 
Point [ x : 10, y : 90 , length : 90.5539 ]]

Projection direct on member variable m_x [ 
Point [ x : 10, y : 90 , length : 90.5539 ] 
Point [ x : 20, y : 80 , length : 82.4621 ] 
Point [ x : 30, y : 70 , length : 76.1577 ]]
Projections with for_each: 
project the pair::first:  1 2 3
project the pair::first:  1 2 3
project the pair::second:  one two tree

References