14.1. Basic Concepts
FundamentalOverloaded operators are functions with special names: the keyword operator
followed by the symbol for the operator being defined. Like any other function, an overloaded operator has a return type, a parameter list, and a body.
An overloaded operator function has the same number of parameters as the operator has operands. A unary operator has one parameter; a binary operator has two. In a binary operator, the left-hand operand is passed to the first parameter and the right-hand operand to the second. Except for the overloaded function-call operator, operator()
, an overloaded operator may not have default arguments (§ 6.5.1, p. 236).
If an operator function is a member function, the first (left-hand) operand is bound to the implicit this
pointer (§ 7.1.2, p. 257). Because the first operand is implicitly bound to this
, a member operator function has one less (explicit) parameter than the operator has operands.
INFO
When an overloaded operator is a member function, this
is bound to the left-hand operand. Member operator functions have one less (explicit) parameter than the number of operands.
An operator function must either be a member of a class or have at least one parameter of class type:
// error: cannot redefine the built-in operator for ints
int operator+(int, int);
This restriction means that we cannot change the meaning of an operator when applied to operands of built-in type.
We can overload most, but not all, of the operators. Table 14.1 shows whether or not an operator may be overloaded. We’ll cover overloading new
and delete
in § 19.1.1 (p. 820).
Operators That May Be Overloaded:
+ - * / % ^
& | ~ ! , =
< > <= >= ++ --
<< >> == != && ||
+= -= /= %= ^= &=
|= *= <<= >>= [] ()
-> ->* new new[] delete delete[]
Operators That Cannot Be Overloaded:
:: .* . ?:
We can overload only existing operators and cannot invent new operator symbols. For example, we cannot define operator**
to provide exponentiation.
Four symbols (+
, -
, *
, and &
) serve as both unary and binary operators. Either or both of these operators can be overloaded. The number of parameters determines which operator is being defined.
An overloaded operator has the same precedence and associativity (§ 4.1.2, p. 136) as the corresponding built-in operator. Regardless of the operand types
x == y + z;
is always equivalent to x == (y + z)
.
Calling an Overloaded Operator Function Directly
Ordinarily, we “call” an overloaded operator function indirectly by using the operator on arguments of the appropriate type. However, we can also call an overloaded operator function directly in the same way that we call an ordinary function. We name the function and pass an appropriate number of arguments of the appropriate type:
// equivalent calls to a nonmember operator function
data1 + data2; // normal expression
operator+(data1, data2); // equivalent function call
These calls are equivalent: Both call the nonmember function operator+
, passing data1
as the first argument and data2
as the second.
We call a member operator function explicitly in the same way that we call any other member function. We name an object (or pointer) on which to run the function and use the dot (or arrow) operator to fetch the function we wish to call:
data1 += data2; // expression-based ''call''
data1.operator+=(data2); // equivalent call to a member operator function
Each of these statements calls the member function operator+=
, binding this
to the address of data1
and passing data2
as an argument.
Some Operators Shouldn’t Be Overloaded
Recall that a few operators guarantee the order in which operands are evaluated. Because using an overloaded operator is really a function call, these guarantees do not apply to overloaded operators. In particular, the operand-evaluation guarantees of the logical AND, logical OR (§ 4.3, p. 141), and comma (§ 4.10, p. 157) operators are not preserved. Moreover, overloaded versions of &&
or ||
operators do not preserve short-circuit evaluation properties of the built-in operators. Both operands are always evaluated.
Because the overloaded versions of these operators do not preserve order of evaluation and/or short-circuit evaluation, it is usually a bad idea to overload them. Users are likely to be surprised when the evaluation guarantees they are accustomed to are not honored for code that happens to use an overloaded version of one of these operators.
Another reason not to overload comma, which also applies to the address-of operator, is that unlike most operators, the language defines what the comma and address-of operators mean when applied to objects of class type. Because these operators have built-in meaning, they ordinarily should not be overloaded. Users of the class will be surprised if these operators behave differently from their normal meanings.
TIP
Best Practices
Ordinarily, the comma, address-of, logical AND, and logical OR operators should not be overloaded.
Use Definitions That Are Consistent with the Built-in Meaning
When you design a class, you should always think first about what operations the class will provide. Only after you know what operations are needed should you think about whether to define each operation as an ordinary function or as an overloaded operator. Those operations with a logical mapping to an operator are good candidates for defining as overloaded operators:
- If the class does IO, define the shift operators to be consistent with how IO is done for the built-in types.
- If the class has an operation to test for equality, define
operator==
. If the class hasoperator==
, it should usually haveoperator!=
as well. - If the class has a single, natural ordering operation, define
operator<
. If the class hasoperator<
, it should probably have all of the relational operators. - The return type of an overloaded operator usually should be compatible with the return from the built-in version of the operator: The logical and relational operators should return
bool
, the arithmetic operators should return a value of the class type, and assignment and compound assignment should return a reference to the left-hand operand.
Assignment and Compound Assignment Operators
Assignment operators should behave analogously to the synthesized operators: After an assignment, the values in the left-hand and right-hand operands should have the same value, and the operator should return a reference to its left-hand operand. Overloaded assignment should generalize the built-in meaning of assignment, not circumvent it.
INFO
Caution: Use Operator Overloading Judiciously
Each operator has an associated meaning from its use on the built-in types. Binary +
, for example, is strongly identified with addition. Mapping binary +
to an analogous operation for a class type can provide a convenient notational shorthand. For example, the library string
type, following a convention common to many programming languages, uses +
to represent concatenation—“adding” one string
to the other.
Operator overloading is most useful when there is a logical mapping of a built-in operator to an operation on our type. Using overloaded operators rather than inventing named operations can make our programs more natural and intuitive. Overuse or outright abuse of operator overloading can make our classes incomprehensible.
Obvious abuses of operator overloading rarely happen in practice. As an example, no responsible programmer would define operator+
to perform subtraction. More common, but still inadvisable, are uses that contort an operator’s “normal” meaning to force a fit to a given type. Operators should be used only for operations that are likely to be unambiguous to users. An operator has an ambiguous meaning if it plausibly has more than one interpretation.
If a class has an arithmetic (§ 4.2, p. 139) or bitwise (§ 4.8, p. 152) operator, then it is usually a good idea to provide the corresponding compound-assignment operator as well. Needless to say, the +=
operator should be defined to behave the same way the built-in operators do: it should behave as +
followed by =
.
Choosing Member or Nonmember Implementation
When we define an overloaded operator, we must decide whether to make the operator a class member or an ordinary nonmember function. In some cases, there is no choice—some operators are required to be members; in other cases, we may not be able to define the operator appropriately if it is a member.
The following guidelines can be of help in deciding whether to make an operator a member or an ordinary nonmember function:
- The assignment (
=
), subscript ([]
), call (()
), and member access arrow (->
) operators must be defined as members. - The compound-assignment operators ordinarily ought to be members. However, unlike assignment, they are not required to be members.
- Operators that change the state of their object or that are closely tied to their given type—such as increment, decrement, and dereference—usually should be members.
- Symmetric operators—those that might convert either operand, such as the arithmetic, equality, relational, and bitwise operators—usually should be defined as ordinary nonmember functions.
Programmers expect to be able to use symmetric operators in expressions with mixed types. For example, we can add an int
and a double
. The addition is symmetric because we can use either type as the left-hand or the right-hand operand. If we want to provide similar mixed-type expressions involving class objects, then the operator must be defined as a nonmember function.
When we define an operator as a member function, then the left-hand operand must be an object of the class of which that operator is a member. For example:
string s = "world";
string t = s + "!"; // ok: we can add a const char* to a string
string u = "hi" + s; // would be an error if + were a member of string
If operator+
were a member of the string
class, the first addition would be equivalent to s.operator+("!")
. Likewise, "hi" + s
would be equivalent to "hi".operator+(s)
. However, the type of "hi"
is const char*
, and that is a built-in type; it does not even have member functions.
Because string
defines +
as an ordinary nonmember function, "hi" + s
is equivalent to operator+("hi", s)
. As with any function call, either of the arguments can be converted to the type of the parameter. The only requirements are that at least one of the operands has a class type, and that both operands can be converted (unambiguously) to string
.
INFO
Exercises Section 14.1
Exercise 14.1: In what ways does an overloaded operator differ from a built-in operator? In what ways are overloaded operators the same as the built-in operators?
Exercise 14.2: Write declarations for the overloaded input, output, addition, and compound-assignment operators for Sales_data
.
Exercise 14.3: Both string
and vector
define an overloaded ==
that can be used to compare objects of those types. Assuming svec1
and svec2
are vector
s that hold string
s, identify which version of ==
is applied in each of the following expressions:
(a)"cobble" == "stone"
(b)svec1[0] == svec2[0]
(c)svec1 == svec2
(d)"svec1[0] == "stone"
Exercise 14.4: Explain how to decide whether the following should be class members:
(a)%
(b)%=
(c)++
(d)->
(e)<<
(f)&&
(g)==
(h)()
Exercise 14.5: In exercise 7.40 from § 7.5.1 (p. 291) you wrote a sketch of one of the following classes. Decide what, if any, overloaded operators your class should provide.
(a)Book
(b)Date
(c)Employee
(d)Vehicle
(e)Object
(f)Tree