05 - compound assignment

Compound assignment operators can be defined both as free functions and as member functions. Member functions are preferred because their implementation is simpler and since the operation has no symmetry (a += b is not the same as b += a for non-numeric abstractions, e.g. strings) there are no problems with inconsistent implicit convertion.

Both free and member function implementations can return a reference to allow chained operations like a = b = c = d (most commonly done with operators << and >>) (member function uses return *this; statement, free function returns first parameter which for the cause of modification is taken by non-const reference).

To avoid code duplication, there are 2 choices:

  • compound assignments reuse other operators

  • other operators reuse compound assignments

The second one is preferred. Reason: just simpler and less code to write.

Updated code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <iostream>
#include <cassert>

// (greatest common divisor)
// if you have C++17, you can remove this function and use std::gcd from <numeric>
int gcd(int a, int b)
{
	if (b == 0)
		return a;
	else
		return gcd(b, a % b);
}

class fraction
{
private:
	int m_numerator;
	int m_denominator;

	static int make_valid_denominator(int value)
	{
		if (value == 0)
			return 1;
		else
			return value;
	}

public:
	fraction(int numerator = 0, int denominator = 1)
	: m_numerator(numerator)
	, m_denominator(make_valid_denominator(denominator))
	{}

	void simplify()
	{
		const int n = gcd(m_numerator, m_denominator);
		m_numerator /= n;
		m_denominator /= n;
	}

	int numerator() const { return m_numerator; }
	int denominator() const { return m_denominator; }

	fraction& operator+=(fraction rhs)
	{
		m_numerator = numerator() * rhs.denominator() + rhs.numerator() * denominator();
		m_denominator = denominator() * rhs.denominator();

		simplify();
		return *this;
	}

	fraction& operator-=(fraction rhs)
	{
		m_numerator = numerator() * rhs.denominator() - rhs.numerator() * denominator();
		m_denominator = denominator() * rhs.denominator();

		simplify();
		return *this;
	}

	fraction& operator*=(fraction rhs)
	{
		m_numerator *= rhs.numerator();
		m_denominator *= rhs.denominator();

		simplify();
		return *this;
	}

	fraction& operator/=(fraction rhs)
	{
		// note: we could write assert(rhs != 0) but
		// at this point in code operator!= has not been defined yet
		// a different solution would be to declare operator/= and implement it later
		assert(rhs.numerator() != 0);
		m_numerator *= rhs.denominator();
		m_denominator *= rhs.numerator();

		simplify();
		return *this;
	}

	fraction& operator%=(fraction rhs)
	{
		assert(rhs.numerator() != 0);
		m_numerator = (numerator() * rhs.denominator()) % (rhs.numerator() * denominator());
		m_denominator *= rhs.denominator();

		simplify();
		return *this;
	}
};

bool operator==(fraction lhs, fraction rhs)
{
	if (lhs.denominator() == rhs.denominator())
		return lhs.numerator() == rhs.numerator();

	// a/b == c/d is same as ad/bd == bc/bd
	// we don't need to compute new denominators, just compare ad and bc
	return lhs.numerator() * rhs.denominator() == rhs.numerator() * lhs.denominator();
}

bool operator!=(fraction lhs, fraction rhs)
{
	return !(lhs == rhs);
}

// Just look how short these are!
// The expression modifies lhs which is then returned by reference (from
// compound version) which is then returned by value from the non-compound version.
// If the type is expensive, second parameter should be taken by const reference
// (first by value because it has to be copied, modified and returned).
fraction operator+(fraction lhs, fraction rhs) { return lhs += rhs; }
fraction operator-(fraction lhs, fraction rhs) { return lhs -= rhs; }
fraction operator*(fraction lhs, fraction rhs) { return lhs *= rhs; }
fraction operator/(fraction lhs, fraction rhs) { return lhs /= rhs; }
fraction operator%(fraction lhs, fraction rhs) { return lhs %= rhs; }

int main()
{
	assert(fraction(1, 2) + fraction(1, 4) == fraction(3, 4));
	assert(fraction(1, 2) - fraction(1, 4) == fraction(1, 4));

	assert(fraction(3, 5) * fraction(4, 3) == fraction(4, 5));
	assert(fraction(3, 5) / fraction(3, 4) == fraction(4, 5));

	assert(fraction(3, 5) * fraction(4, -3) == fraction(-4, 5));
	assert(fraction(3, 5) / fraction(3, -4) == fraction(-4, 5));

	assert(fraction(2, 1) % fraction(3, 10) == fraction(1, 5));
	assert(fraction(2, 1) % fraction(3, -10) == fraction(1, 5));
	assert(fraction(2, 1) % fraction(-3, 10) == fraction(1, 5));
	assert(fraction(-2, 1) % fraction(3, 10) == fraction(-1, 5));
	assert(fraction(2, -1) % fraction(3, 10) == fraction(-1, 5));
}