Effective C++ item 24: Declare Non-member Functions When Type Conversions Should Apply to All Parameters

If we define a Rational class to represent rational numbers like following

1
2
3
4
5
6
7
8
9
10
class Rational {
public:
Rational(int numerator = 0,
int denominator = 1);
int numerator() const;
int denominator() const;
private:
int m_numerator;
int m_denominator;
};

And you want to support rational number arithmetic like mutiplication, your instinct tells you to implement it as a member function

1
2
3
4
5
class Rational {
public:
...
const Rational operator*(const Rational& rhs) const;
};

This seems ok but actually when you try to use it, you will realize there is an issue

1
2
3
4
5
6
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEighth; // fine
result = result * oneEighth; // fine
result = oneHalf * 2; // fine
result = 2 * oneHalf; // error!

The last operation doesn’t compile! It’s because the last line is equivalent to result = 2.operator*(oneHalf) which is clearly an error. There is no operator* defined for literal 2.

If you define the constructor explicit, then you will realize result = oneHalf * 2 won’t compile either because it fails to apply implicit type conversion to 2.

What’s the solution? You need to define operator* as non-member function, which will allow compiler to perform implicit type conversion for all arguments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Rational {
public:
Rational(int numerator = 0,
int denominator = 1);
int numerator() const;
int denominator() const;
private:
int m_numerator;
int m_denominator;
};
const Rational operator*(const Rational& lhs,
const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}

This way, all usage above will compile and work correctly.

Reference:
“Effective C++” Third Edition by Scott Meyers.