11.3. Overloaded Operators As friend Functions

Time: 00:05:32 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)
Review

For most kinds of numerical data, the addition operation is commutative: the result doesn't depend on the operands' order. To make the fraction addition operation, operator+, fully commutative, it must function correctly in three different cases. Let f1 and f2 be two fraction objects, and i be an integer:

  1. f1 + f2
  2. f1 + i
  3. i + f1

In the previous section, we created two overloaded operators, fraction::operator+(fraction) and fraction::operator+(int), satisfying the first two requirements. However, neither overloaded operator can satisfy the third requirement. It isn't necessary to deal with the case of two integer operands - the "normal" addition operation works for fundamental data types.

Requirement 1Requirement 2Requirement 3
A picture illustrating the equivalence of f1 + f2 = f1.operator+(f2) A picture illustrating the equivalence of f2 + f1 = f2.operator+(f1) A picture illustrating the equivalence of f1 + i = f1.operator(i) A picture illustrating the equivalence of i + f2 = i.operator(f2)
(a) (b) (c)
Commutativity and overloaded operators. Once defined as a member function, programs can use overloaded operators with two different syntaxes. The first, used in practice, reflects the "natural" way we use the operators when writing arithmetic formulas but obscures a significant implementation detail: which object is bound to the function. The second, not used in practice, unambiguously differentiates which operand is bound to the function and which is the function's argument. However, this syntax doesn't look like operators appearing in arithmetic formulas and doesn't offer an advantage over a "normal" function call.
  1. Both operand orders match the first prototype, fraction operator+(fraction f);, created in the previous section.
  2. This operand order matches the second prototype, fraction fraction::operator+(int i);, created in the previous section.
  3. Commutativity suggests that swapping the addition operands should be valid: While i + f2 is arithmetically valid, the syntax i.operator(f2) is not - the dot operator's left-hand operand must be an object. This problem cannot be solved with any member function.
Like addition, multiplication is commutative, and the above discussion also applies to it. Although subtraction and division are not commutative, the operations i - f1 and i / f1 are valid, making the discussion relevant for them as well.

friend Functions

Summarizing our progress to this point:

Non-Member (friend) Function Partial fraction class
A picture showing how the expression 'i + f2' maps to the correct non-member function call 'operator+(i, f2)'.
class fraction
{
    private:
        int    numerator;
        int    denominator;

    public:
        fraction operator+(fraction f);			// (1)
        fraction operator+(int i);			// (2)
        friend fraction operator+(int i, fraction f2);	// (3)
};
(a)(b)
Non-member overloaded operators. Implementing overloaded operators as non-member functions satisfies the third operand pattern. Making the function a friend allows it to access an object's private members.
  1. When implemented as a non-member function, both operands are passed as explicit arguments.
  2. Each overloaded operator satisfies one of the three required operator patterns.
friend functions, like overloaded operators, are controversial and disliked by some. Nevertheless, they provide an elegant solution when rigidly enforcing encapsulation results in inelegant code or fails to solve a necessary problem.

One Function To Rule Them All

Although the three overloaded operators correctly process all possible addition operand patterns, they seem unnecessarily cumbersome and verbose. Fortunately, given one prerequisite, we can collapse the three overloaded operators into a single function. That prerequisite is a conversion constructor that converts an integer to a fraction object. Numerically, any integer, i, is equivalent to the fraction i/1, a conversion the original fraction constructor performs.

class fraction
{
	private:
		int	numerator;
		int	denominator;

	public:
		fraction(int n = 0, int d = 1) : numerator(n), denominator(d) {}

		friend fraction operator+(fraction f1, fraction f2);
};
A picture illustrating the equivalence of f1 + f2 = operator+(f1, f2) A picture illustrating the equivalence of f2 + f1 = operator+(f2, f1) A picture illustrating the equivalence of f1 + i = operator(f1, i) A picture illustrating the equivalence of i + f2 = operator(i, f2)
(a)(b)
A single friend overloaded operator+. This version of the operator+ highlights the significant features of the function. It must be a friend function, and both operands are fractions. Nevertheless, it correctly processes all required operand patterns.
  1. The operands (i.e., arguments) match the operator+ signature, and the program calls the addition function without converting either operand.
  2. One operand is an integer. Anthropomorphically, the program might say, "If the integer was a fraction, I could add them. I know how to convert an integer to a fraction. So, I will convert the integer to a fraction and add them together."
The program will only perform one automatic conversion operation and will not chain multiple conversion operations.