Insight 20.3.1.
When the compiler sees something like
data1 + data2
, it will attempt to turn that into data1.operator+(data2)
.
3/4
or 5/2
. Below is a simple class Rational
to represent a rational number by storing a numerator and a denominator. None of this code is doing anything special yet, so donβt worry about reading it too closely.
#include <format>
#include <iostream>
#include <numeric>
#include <stdexcept>
#include <string>
using namespace std;
class Rational {
private:
int m_numerator;
int m_denominator;
public:
Rational(int numerator, int denominator);
int getNumerator() const;
int getDenominator() const;
double doubleValue() const;
string toString() const;
// Make a new Rational object that is the simplified version of this one
Rational simplify() const;
// Some functions we will build later...
// Non-operator functions
Rational add(const Rational& other) const;
bool equals(const Rational& other) const;
// Operators versions of those functions
Rational operator+(const Rational& other) const;
bool operator==(const Rational& other) const;
// Other operators
Rational& operator++(); // Prefix increment
Rational operator++(int); // Postfix increment
Rational& operator--(); // Prefix decrement
Rational operator--(int); // Postfix decrement
};
Rational::Rational(int numerator, int denominator) {
if (denominator == 0) {
throw invalid_argument("Denominator cannot be zero.");
}
m_numerator = numerator;
m_denominator = denominator;
}
int Rational::getNumerator() const {
return m_numerator;
}
int Rational::getDenominator() const {
return m_denominator;
}
double Rational::doubleValue() const {
return static_cast<double>(m_numerator) / m_denominator;
}
string Rational::toString() const {
string stringRep = format("{}/{}", m_numerator, m_denominator);
return stringRep;
}
Rational Rational::simplify() const {
int divisor = std::gcd(abs(m_numerator), abs(m_denominator));
int newNumerator = m_numerator / divisor;
int newDenominator = m_denominator / divisor;
// - sign should only be in the numerator
if (newDenominator < 0) {
newNumerator = -newNumerator;
newDenominator = -newDenominator;
}
return Rational(newNumerator, newDenominator);
}
Rational
objects like this:
Rational Rational::add(const Rational& other) const {
int newNumerator = m_numerator * other.m_denominator
+ other.m_numerator * m_denominator;
int newDenominator = m_denominator * other.m_denominator;
Rational result(newNumerator, newDenominator);
return result.simplify();
}
bool Rational::equals(const Rational& other) const {
return m_numerator * other.m_denominator
== other.m_numerator * m_denominator;
}
add
is called on one Rational
and given a second Rational
as a parameter. It returns a new Rational
that is the sum of the two without modifying either of the starting values. Thus the right way to use add
on two Rationals
named r1
and r2
would be:
Rational r3 = r1.add(r2);
equals
is called on one Rational
and the other is passed as a parameter: r1.equals(r2)
.
operator+
and operator==
. This sample has those functions and a main
to test them:
r1 + r2
and r1 == r2
in the above code, it translates them into r1.operator+(r2)
and r1.operator==(r2)
. This is what we mean by syntactic sugar... the operators are really just regular member functions, but there is some syntax magic happening to make the code look simpler.
r1 + r2
to r1.operator+(r2)
in the main function. The code will run exactly the same!
data1 + data2
, it will attempt to turn that into data1.operator+(data2)
.
+
can be applied to the other arithmetic symbols, such as -
, *
, and /
. We also can take use ==
as a template to write other relational operators like !=
, <
, >=
, etc... For each of the operators, we just need to figure out what logic can be done with the member variables to generate the correct answer.