This set of slides introduces the reader to the concept of operator overloading for user-defined types in C++ (with elements of C++11 and C++14). The exemplary case of the complex class is introduced. It follows a discussion on how to implement mixed-mode arithmetic, which requires mixing member and non-member operator functions. Moreover, the technical tool of friend functions and access functions is discussed.
5. A class for complex numbers
Let us now assume that we need to perform some arithme2c on
complex numbers
6. A class for complex numbers
Since there is no built-in types modeling the concept of complex
numbers, we decide to introduce a new class1
1
In fairness, there already exists in the Standard Library the class std::complex
7. The complex type
class complex {
public:
complex(double re,
double im): re(re), im(im) {}
private:
double re;
double im;
};
8. The complex type
We can now define values of of type complex:
complex a(3, 5);
complex b(7, 1);
10. Suppor&ng addi&on
It seems reasonable to add a member func*on named plus
class complex {
public:
... plus (...);
};
11. Suppor&ng addi&on
From mathema*cs, we know that adding two complex values x
and y results in a new complex value z
class complex {
public:
complex plus(...);
};
12. Suppor&ng addi&on
Moreover, adding two complex values x and y does not require
any change in either x or y
class complex {
public:
complex plus(complex const& y);
};
14. Suppor&ng addi&on
We can now add together two complex values
complex a(3, 5);
complex b(7, 1);
complex c = a.plus(b);
15. Syntac'c noise
Although the above solu.on works, highly-nested expressions
might be difficult to parse due to syntac'c noise
a.plus(b.plus(c)).plus(d);
19. Conven&onal nota&on
A"er all, such an expression is the result of hundreds of years of
experience with mathema/cal nota/on
(a + (b + c)) + d;
20. Operators on built-in types
This is why C++ supports a set of operators for its built-in types
float a = 2, b = 3,
c = 1, d = 10;
(a + (b + c)) + d;
21. Operators on UDTs
However, most concepts for which operators are conven3onally
used are not built-in types
22. Operators on UDTs
Just to name a few, the primary way of interac4ng with matrices
and complex numbers is through the use of operators on them
26. Language of the problem domain
Users of our class can then program in the language of the problem
domain, rather than in the language of the machine
complex a(3, 5);
complex b(7, 1);
complex c = a + b; // a.plus(b);
28. Defining operator+
Hence, we can overload operator + as follows:
class complex {
...
complex operator+(complex const& y);
};
29. Defining operator+
From an implementa-on viewpoint, operator+ does not differ
from our previous plus func-on
complex operator+(complex const& y) {
return complex(re + y.re,
im + y.im);
}
30. Operator func-ons
An operator func-on can be called like any other func-on2
complex c = a + b; // shorthand
complex c = a.operator+(b); // explicit call
2
However, note that only operators on user-defined types can be explicitly invoked. That is, we cannot do
int a = 3; int b = a.operator+(5);
31. The complex type
class complex {
public:
complex(double re,
double im): re(re), im(im) {}
complex operator+(complex const& y) {...}
private:
double re;
double im;
};
33. Adding complexes and doubles
What happens when we try to do the following?
complex a(3, 5);
complex c = a + 2.3;
34. Adding complexes and doubles
We incur in a compile-)me error
complex a(3, 5);
// error: no match for 'operator+'
// (operand types are 'complex' and 'double')
complex c = a + 2.3;
35. Mixed-mode addi+on
We never defined addi+on between complex and double values
// error: no match for 'operator+'
complex c = a + 2.3;
36. An overload for doubles
We introduce an addi-onal overload for the specific case of right-
hand side operands of type double3
complex operator+(double const& y);
3
Here, we decided to use a pass-by-const-reference approach to stress the fact that addi7on is a non-muta7ng
opera7ons for both the operands. However, pass-by-value would have been an equally acceptable solu7on
37. An overload for doubles
We introduce an addi-onal overload for the specific case of right-
hand side operands of type double3
complex operator+(double const& y) {
return complex(re + y, im);
}
3
Here, we decided to use a pass-by-const-reference approach to stress the fact that addi7on is a non-muta7ng
opera7ons for both the operands. However, pass-by-value would have been an equally acceptable solu7on
38. An overload for doubles
Apparently, we can now perform mixed-mode arithme6c
complex a(3, 5);
complex c = a + 2.3; // it now compiles!
39. Commuta'vity of addi'on
However, users of our class expect addi4on to be commuta've
complex a(3, 5);
(a + 2.3) == (2.3 + a); // true!
40. Adding doubles and complexes
What happens if we do the following?
complex a(3, 5);
complex c = 2.3 + a;
41. Adding doubles and complexes
complex a(3, 5);
// error: no match for 'operator+'
// (operand types are 'double' and 'complex')
complex c = 2.3 + a;
42. Adding doubles and complexes
If double were a class, we could write a version of operator+
tailored for right-hand side operands of type complex
struct double {
...
complex operator+(complex const& y);
};
43. Non-member operator func0ons
Given that double is in fact a built-in type, we need an alterna4ve
way of defining addi4on with complex values
44. Non-member operator func0ons
For any binary operator ~, a~b can be interpreted as either
• a.operator~(b), or
• operator~(a, b)4
4
With the no*ceably excep*on, as we will see, of the assignment operator
45. Non-member operator func0ons
That is, a binary operator can be defined by either
• a member func*on taking one argument, or
• a non-member func1on taking two arguments
46. Adding doubles and complexes
The resul)ng non-member operator func)on is as follows:
complex operator+(double const& x, complex const& y);
48. Non-member operator func0ons
Intui&vely, we would like to write the following
complex operator+(double const& x, complex const& y) {
return complex(y.re + x, y.im);
}
49. Non-member operator func0ons
Since we are wri*ng a non-member func*on, we cannot directly
access the private members y.re and y.im
complex operator+(double const& x, complex const& y) {
return complex(y.re + x, y.im);
}
50. Non-member operator func0ons
Two solu(ons to this problem:
• Declare the non-member operator as a friend of the class
• Provide complex with access func9ons
51. Declaring func-ons as friends
The friend declara*on grants a func*on access to private
members of the class in which such a friend declara*on appears
56. Non-member operator func0on
How to use our new access func.ons in order to implement
operator+?
complex operator+(double const& x, complex const& y);
57. Non-member operator func0on
How to use our new access func.ons in order to implement
operator+?
complex operator+(double const& x, complex const& y) {
return complex(x + y.real(), y.imag());
}
58. Non-member operator func0on
Unfortunately, our solu/on does not compile
// error: member function 'real' not viable:
// 'this' argument has type 'const complex',
// but function is not marked const
59. Const-correctness
Since we know that addi.on is a non-muta.ng opera.ons, we
correctly marked x and y as const
complex operator+(double const& x, complex const& y) {
return complex(x + y.real(), y.imag());
}
60. Const-correctness
However, we did not inform the compiler that the access func6ons
real() and imag() will not modify either y.re or y.im
61. Const-correctness
Hence, we cannot invoke either real() or imag() on a const
complex value
complex const a(3, 5);
std::cout << a.real(); // compile-time error!
std::cout << a.imag(); // compile-time error!
62. Const access func,ons
In order to do so, we mark as const both real() and imag()
class complex {
public:
...
double real() const { return re; }
double imag() const { return im; }
};
63. Constant operator func.ons
Moreover, we no*ce that we can do the same for the two member
variants of operator+
class complex {
public:
...
complex operator+(complex const& y) const {...}
complex operator+(double const& y) const {...}
};
64. Constant operator func.ons
This allows us to enforce that addi2on is a non-muta(ng
opera2ons for both the operands
class complex {
public:
...
complex operator+(complex const& y) const {...}
complex operator+(double const& y) const {...}
};
69. Equality
We get yet another compile-)me error
complex a(3, 5);
// error: invalid operands to binary expression
// ('complex' and 'complex')
(a + 2.3) == (2.3 + a);
70. Equality
This is because we never defined equality between complex
values
complex a(3, 5);
a == a;
72. Overloading operator==
Specifically, we opt for the non-member version of operator== in
order to enforce the symmetry of equality
bool operator==(complex const& x, complex const& y);
73. Overloading operator==
Fortunately, the implementa1on of operator== is straigh5orward
bool operator==(complex const& x, complex const& y) {
return x.real() == y.real() &&
x.imag() == y.imag();
}
75. Inequality
Therefore, it is not sufficient to overload equality. We must
overload inequality as well
bool operator!=(complex const& x, complex const& y);
76. Inequality
Therefore, it is not sufficient to overload equality. We must
overload inequality as well
bool operator!=(complex const& x, complex const& y) {
return !(x == y);
}
81. I/O operators
Besides the I/O of built-in types and std::strings, iostream
allows programmers to define I/O for their own types
complex a(3, 5);
std::cout << a;
82. I/O operators
I/O on user-defined types is possible by overloading, respec9vely,
operator<< and operator>>
83. Output operator
Let us focus on the output operator operator<<
complex a(3, 5);
std::cout << a;
85. Non-member output operator
Since our type appears as the right-hand side operands, we need
to specify a non-member operator func4on
void operator<<(..., complex const& x);
86. Non-member output operator
Given that std::cout is a global object of type std::ostream,
and that outpu2ng is a muta-ng opera5on, we obtain
void operator<<(std::ostream& os, complex const& x);
87. Non-member output operator
Apparently, the following suffices
void operator<<(std::ostream& os, complex const& x) {
os << x.real() << " + "
<< x.imag() << "i";
}
94. User-defined literals
In addi'on, with C++11 and above we can define literals for user-
defined types
// 3.5i is a user-defined literal
complex a = 3.5i;
97. Literal operators
The literal operator is a non-member func4on named operator""
followed by the suffix5
complex operator"" i(long double x) {
return complex(0, x);
}
5
Note that, C++14 introduced the i literal suffix to match the std::complex type. Therefore, in real code we
should not overload it manually
98. Language of the problem domain
We can now write
complex a = 7.1 + 3.5i;
std::cout << a;
99. Language of the problem domain
For the case of complex values, our new user-defined literal helps
us ge8ng even closer to the language of the problem domain
complex a = 7.1 + 3.5i;
std::cout << a;
101. Bibliography
• B. Stroustrup, The C++ Programming Language (4th
ed.)
• B, Stroustrup, Programming: Principles and Prac@ce
Using C++ (2nd
ed.)
• C++ Reference, Operator overloading
• ISO C++, C++ FAQ