Item 21: Overload to avoid implicit type conversions.
Here's some code that looks nothing if not eminently
class UPInt { // class for unlimited public: // precision integers UPInt(); UPInt(int value); ... }; // For an explanation of why the return value is const, // see Item E21 const UPInt operator+(const UPInt& lhs, const UPInt& rhs); UPInt upi1, upi2; ... UPInt upi3 = upi1 + upi2;
There are no surprises here. upi1
and upi2
are both UPInt
objects, so adding them together just calls operator+
for UPInts
.
Now consider these
upi3 = upi1 + 10; upi3 = 10 + upi2;
These statements also succeed. They do so through the creation of temporary objects to convert the integer 10 into UPInts
(see Item 19).
It is convenient to have compilers perform these kinds of conversions, but the temporary objects created to make the conversions work are a cost we may not wish to bear. Just as most people want government benefits without having to pay for them, most C++ programmers want implicit type conversions without incurring any cost for temporaries. But without the computational equivalent of deficit spending, how can we do
We can take a step back and recognize that our goal isn't really type conversion, it's being able to make calls to operator+
with a combination of UPInt
and int
arguments. Implicit type conversion happens to be a means to that end, but let us not confuse means and ends. There is another way to make mixed-type calls to operator+
succeed, and that's to eliminate the need for type conversions in the first place. If we want to be able to add UPInt
and int
objects, all we have to do is say so. We do it by declaring several functions, each with a different set of parameter
const UPInt operator+(const UPInt& lhs, // add UPInt const UPInt& rhs); // and UPInt const UPInt operator+(const UPInt& lhs, // add UPInt int rhs); // and int const UPInt operator+(int lhs, // add int and const UPInt& rhs); // UPInt UPInt upi1, upi2; ... UPInt upi3 = upi1 + upi2; // fine, no temporary for // upi1 or upi2 upi3 = upi1 + 10; // fine, no temporary for // upi1 or 10 upi3 = 10 + upi2; // fine, no temporary for // 10 or upi2
Once you start overloading to eliminate type conversions, you run the risk of getting swept up in the passion of the moment and declaring functions like
const UPInt operator+(int lhs, int rhs); // error!
The thinking here is reasonable enough. For the types UPInt
and int
, we want to overload on all possible combinations for operator+
. Given the three overloadings above, the only one missing is operator+
taking two int
arguments, so we want to add
Reasonable or not, there are rules to this C++ game, and one of them is that every overloaded operator must take at least one argument of a user-defined type. int
isn't a user-defined type, so we can't overload an operator taking only arguments of that type. (If this rule didn't exist, programmers would be able to change the meaning of predefined operations, and that would surely lead to chaos. For example, the attempted overloading of operator+
above would change the meaning of addition on int
s. Is that really something we want people to be able to
Overloading to avoid temporaries isn't limited to operator functions. For example, in most programs, you'll want to allow a string
object everywhere a char*
is acceptable, and vice versa. Similarly, if you're using a numerical class like complex
(see Item 35), you'll want types like int
and double
to be valid anywhere a numerical object is. As a result, any function taking arguments of type string
, char*
, complex
, etc., is a reasonable candidate for overloading to eliminate type
Still, it's important to keep the 80-20 rule (see Item 16) in mind. There is no point in implementing a slew of overloaded functions unless you have good reason to believe that it will make a noticeable improvement in the overall efficiency of the programs that use