Chapter 14. Overloaded Operations and Conversions
14.1 Basic Concepts
Overloaded operators are functions with special names: the key word operator followed by the symbol for the operator being defined.
When an overloaded operator is a member function, this is bound to the left-hand operand. Member operator functions have one less parameter than the number of operands.
Operators
+
-
*
/
%
^
&
|
~
!
,
=
<
>
<=
>=
++
--
<<
>>
==
!=
&&
||
+=
-=
/=
%=
^=
&=
|=
*=
<<=
>>=
[]
()
->
->*
new
new[]
delete
delete[]
::
.*
.
?:
where the ::
, .*
, .
, ?:
cannot be overloaded.
Ordinarily, we "call" an overloaded operator function indirectly by using the operator on arguments of the appropriate type. We can also call an overloaded operator function directly in the same way that we call an ordinary function.
Overloaded versions of &&
or ||
operators do not preserve short-circuit evaluation properties of the built-in operators.
Ordinarily, the comma, address-of, logical AND, and logical OR operators should not be overloaded.
Assignment operators should behave analogously to the synthesised 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.
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->
operator must be defined as members.The compound-assignment operators ordinarily ought 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.
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.
14.2 Input and Output Operators
14.2.1 Overloading the Output Operator <<
Ordinarily, the first parameter of an output operator is a reference to a non-const ostream
object. The second parameter ordinarily should be a reference to const
of the class type we want to print.
If the operator does print a newline, then users would be unable to print descriptive test along with the object on the same line. Generally, output operators should print the contents of the object, with minimal formatting. They should not print a new line.
Input and output operators that conform to the conventions of the iostream library must be ordinary nonmember function.
14.2.2 Overloading the Input Operator >>
Ordinarily the first parameter of an input operator is a reference to the stream from which it is to read, and the second parameter is a reference to the (non-const) object into which to read.
Input operators must deal with the possibility that the input might fail; output operators generally don't bother.
The kinds of errors that might happen in an input operator include the following:
A read operation might fail because the stream contains data of an incorrect type.
Any of the reads could hit end-of-file or some other error on the input stream.
14.3 Arithmetic and Relational Operators
Ordinarily, we define the arithmetic and relational operators as nonmember functions in order to allow conversions for either the left- or right-hand operand. These operators shouldn't need to change the state of either operand, so the parameters are ordinarily references to const.
14.3.1 Equality Operators
Classes for which there is a logical meaning for equality normally should define operator==
. Classes that define == make it easier for users to use the class with the library algorithm.
14.3.2 Relational Operators
If a single logical definition for <
exists, classes usually should define the <
operator. However, if the class also has ==
, define <
only if the definitions of <
and ==
yield consistent results.
14.4 Assignment Operators
A class can define additional assignment operators that allow other types as the right-hand operand.
Assignment operators can be overloaded. Assignment operators, regardless of parameter type, must be defined as member functions.
Compound assignment operators are not required to be members.
Assignment operators must, and ordinarily compound-assignment operators should, be defined as members. These operators should return a reference to the left-hand operand.
14.5 Subscript Operator
Classes that represent containers from which elements can be retrieved by position often define the subscript operator, operator[]
.
The subscript operator must be a member function.
If a class has a subscript operator, it usually should define two versions: one that returns a plain reference and the other that is a const member and returns a reference to const.
14.6 Increment and Decrement Operators
The increment (++) and decrement (--) operators are most often implemented for iterator classes. These operators let the class move between the elements of a sequence.
Classes that define increment or decrement operators should define both the prefix and postfix versions. These operators usually should be defined as members.
To be consistent with the built-in operators, the prefix operators should return a reference to the incremented or decremented object.
There is one problem with defining both the prefix and postfix operators: Normal overloading cannot distinguish between these operators.
To solve this problem, the postfix versions take an extra parameter of type int. When we use a postfix operator, the compiler supplies 0 as the argument for this parameter.
To be consistent with the built-in operators, the postfix operators should return the old value. That value is returned as a value, not a reference.
If we want to call the postfix version using a function call, then we must pass a value for the integer argument:
14.7 Member Access Operators
The dereference (*) and arrow (->) operators are often used in classes that represent iterators and in smart pointer classes.
Operator arrow must be a member. The dereference operator is not required to be a member but usually should be a member as well.
The overloaded arrow operator must return either a pointer to a class type or an object of a class type that defines its own operator arrow.
14.8 Function-Call Operator
Classes that overload the call operator allow objects of its type to be used as if they were a function.
The function-call operator must be a member function. A class may define multiple versions of the call operator, each of which must differ as to the number or types of their parameters.
14.8.1 Lambdas Are Function Objects
When we write a lambda, the compiler translates that expression into an unnamed object of an unnamed class. The classes generated from a lambda contain an overloaded function-call operator.
Variables that are captured by value are copied into the lambda.
14.8.2 Library-Defined Function Objects
The standard library defines a set of classes that represent the arithmetic, relational, and logical operators.
Arithmetic
Relational
Logical
plus<Type>
equal_to<Type>
logical_and<Type>
minus<Type>
not_equal<Type>
logical_or<Type>
multiplies<Type>
greater<Type>
logical_not<Type>
divides<Type>
greater_equal<Type>
modulus<Type>
less<Type>
negate<Type>
less_equal<Type>
The function-object classes that represent operators are often used to override the default operator used by an algorithm.
One important aspect of these library function objects is that the library guarantees that they will work for pointers.
14.8.3 Callable Objects and function
C++ has several kinds of callable objects: functions and pointers to functions, lambdas, objects created by bind
, and classes that overload the function-call operator.
Two callable objects with different types may share the same call signature. The function table is used to store these callable "pointers".
In C++, function tables are easy to implement using a map
.
We can solve this problem using a new library type name function
that is defined in the functional
header.
We cannot store the name of an overloaded function in an object of type function:
Operations on function
Description
function<T> f;
f is null function object that can store callable objects with a call signature that is equivalent to the function type T.
function<T> f(nullptr);
Explicitly construct a null function.
function<T> f(obj);
Stores a copy of the callable object obj in f.
f
Use f as condition; true if f holds a callable object; false otherwise.
result_type
The type returned by this function type's callable object.
argument_type
Types defined when T has exactly one or two arguments.
first_argument_type
If T has one argument, argument_type is a synonym for that type.
second_argument_type
14.9 Overloading Conversions, and Operators
14.9.1 Conversion Operators
A conversion operator is a special kind of member function that converts a value of a class type to a value of some other type. A conversion function typically has the general form:
where type represents a type.
A conversion function must be a member function, may not specify a return type, and must have an empty parameter list. The function usually should be const.
It is not uncommon for classes to define conversions to bool. Because bool is an arithmetic type, a class-type object that is converted to bool can be used in any context where an arithmetic type is expected.
To prevent such problems, the new standard introduced explicit conversion operators:
An explicit conversion will be used implicitly to convert an expression used as:
The condition of an if, while, or do statement.
The condition expression in a for statement header.
An operand to the logical NOT (!), OR (||), or AND (&&) operators.
The condition expression in a conditional (?:) operator.
Conversion to bool
is usually intended for use in conditions. As a result, operator bool ordinarily should be defined as explicit
.
14.9.2 Avoiding Ambiguous Conversions
There are two ways that multiple conversion paths can occur. The first happens when two classes provide mutual conversions. The second way to generate multiple conversion paths is to define multiple conversions from or to types that are themselves related by conversions.
If two or more conversions provide a viable match, then the conversions are considered equally good.
In a call to an overloaded function, if two user-defined conversion provide a viable match, the conversions are considered equally good.
14.9.3 Function Matching and Overloaded Operators
The set of candidate functions for an operator used in an expression can contain both non-member and member functions.
Last updated
Was this helpful?