Overloaded operators are overloaded functions. Normal function matching (§ 6.4, p. 233) is used to determine which operator—built-in or overloaded—to apply to a given expression. However, when an operator function is used in an expression, the set of candidate functions is broader than when we call a function using the call operator. If a
has a class type, the expression a
sym b
might be
a.operatorsym (b); // a has operatorsym as a member function
operatorsym(a, b); // operatorsym is an ordinary function
Unlike ordinary function calls, we cannot use the form of the call to distinquish whether we’re calling a nonmember or a member function.
Exercise 14.50: Show the possible class-type conversion sequences for the initializations of ex1
and ex2
. Explain whether the initializations are legal or not.
struct LongDouble {
LongDouble(double = 0.0);
operator double();
operator float();
};
LongDouble ldObj;
int ex1 = ldObj;
float ex2 = ldObj;
Exercise 14.51: Show the conversion sequences (if any) needed to call each version of calc
and explain why the best viable function is selected.
void calc(int);
void calc(LongDouble);
double dval;
calc(dval); // which calc?
When we use an overloaded operator with an operand of class type, the candidate functions include ordinary nonmember versions of that operator, as well as the built-in versions of the operator. Moreover, if the left-hand operand has class type, the overloaded versions of the operator, if any, defined by that class are also included.
When we call a named function, member and nonmember functions with the same name do not overload one another. There is no overloading because the syntax we use to call a named function distinguishes between member and nonmember functions. When a call is through an object of a class type (or through a reference or pointer to such an object), then only the member functions of that class are considered. When we use an overloaded operator in an expression, there is nothing to indicate whether we’re using a member or nonmember function. Therefore, both member and nonmember versions must be considered.
The set of candidate functions for an operator used in an expression can contain both nonmember and member functions.
As an example, we’ll define an addition operator for our SmallInt
class:
class SmallInt {
friend
SmallInt operator+(const SmallInt&, const SmallInt&);
public:
SmallInt(int = 0); // conversion from int
operator int() const { return val; } // conversion to int
private:
std::size_t val;
};
We can use this class to add two SmallInt
s, but we will run into ambiguity problems if we attempt to perform mixed-mode arithmetic:
SmallInt s1, s2;
SmallInt s3 = s1 + s2; // uses overloaded operator+
int i = s3 + 0; // error: ambiguous
The first addition uses the overloaded version of +
that takes two SmallInt
values. The second addition is ambiguous, because we can convert 0
to a SmallInt
and use the SmallInt
version of +
, or convert s3
to int
and use the built-in addition operator on int
s.
Providing both conversion functions to an arithmetic type and overloaded operators for the same class type may lead to ambiguities between the overloaded operators and the built-in operators.
Exercise 14.52: Which operator+
, if any, is selected for each of the addition expressions? List the candidate functions, the viable functions, and the type conversions on the arguments for each viable function:
struct LongDouble {
// member operator+ for illustration purposes; + is usually a nonmember
LongDouble operator+(const SmallInt&);
// other members as in § 14.9.2 (p. 587)
};
LongDouble operator+(LongDouble&, double);
SmallInt si;
LongDouble ld;
ld = si + ld;
ld = ld + si;
Exercise 14.53: Given the definition of SmallInt
on page 588, determine whether the following addition expression is legal. If so, what addition operator is used? If not, how might you change the code to make it legal?
SmallInt s1;
double d = s1 + 3.14;