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