The most challenging step in using C++ operator overloading (and in using C++ in general, actually) is probably moving from theory to practice: the theory in textbooks might be clear but its practical application is often arduous.
C++ software developement in practice requires applying a fair deal of conventions, best practices, and professional knowledge whose content is abundant but often poorly digestible, as it is scattered in multiple textbooks, blogs, wikis, Q&A sites.
This document aims at bringing together in a sensible format all the knowledge needed to quickly and effectively fielding and making the most of C++ operator overloading, steering clear, above all, of the many "How could I possibly know that?" traps.
Operator overloading is a kind of polymorphism, available in several programming languages, that allows the developer to define or redefine the behavior of the language operators (e.g. +
, *
, <<
, etc.) for classes and (though discouraged) for primitive data types (e.g. int
, double
, etc.).
Though sometimes belittled as mere syntactic sugar adding nothing to the language expressive power, it is indeed, when properly deployed, an extremely tasteful and energizing sugar, great for closing the gap between the source code and the domain model it is supposed to manipulate.
Short example of operating on hypothetical objects representing 2D euclidean vectors, without operator overloading:
class Vector2D {
// ...
};
Vector2D v1 = // ...
Vector2D v2 = // ...
Vector2D v3 = v1.add(v2).multiply(0.5);
if( ! v1.equal(v3) ) {
v1 = v2.opposite();
}
... and with operator overloading:
class Vector2D {
// ...
};
Vector2D v1 = // ...
Vector2D v2 = // ...
Vector2D v3 = (v1 + v2) * 0.5;
if( v1 != v3 ) {
v1 = -v2;
}
C
- some container-like class
T
- some type, maybe contained in a container-like
C
class I
- some iterator/pointer-like class
X
,Y
,Z
- some type
Operator | Typical signature | Class member? | Notes |
---|---|---|---|
array subscript (non-const) | T & C::operator[](std::size_t idx) |
Must be member |
|
array subscript (const) | const T & C::operator[](std::size_t idx) const |
Must be member |
|
array subscript (for pointer like objects such as random access iterators) | I I::operator[](std::size_t idx) const |
Must be member |
|
dereference (non-const) | Y & X::operator*() |
Should be member |
|
dereference | const Y & X::operator*() const |
Should be member |
|
arrow (non-const) | Y * X::operator->() |
Must be member |
|
arrow (const) | const Y * X::operator->() const |
Must be member |
|
pointer to member | Y & C::operator->*(X x) |
May be member or not |
|
address-of | Y * X::operator&() |
Should be member |
|
Operator | Typical signature | Class member? | Notes |
---|---|---|---|
addition (compound) | X & X::operator+=(const X & other) |
Should be member |
|
subtraction (compound) | X & X::operator-=(const X & other) |
Should be member |
|
multiplication (compound) | X & X::operator*=(const X & other) |
Should be member |
|
division (compound) | X & X::operator/=(const X & other) |
Should be member |
|
modulus (compound) | X & X:operator%=(const X & other) |
Should be member |
|
addition | X operator+(X left, const X & right) |
Should be non-member |
|
subtraction | X operator-(X left, const X & right) |
Should be non-member |
|
multiplication | X operator*(X left, const X & right) |
Should be non-member |
|
division | X operator/(X left, const X & right) |
Should be non-member |
|
modulus | X operator%(X left, const X & right) |
Should be non-member |
|
unary minus | X X::operator-() const |
Should be member |
|
unary plus | X X::operator+() const |
Should be member |
|
// addition
X operator+(X left, const X & right)
{
left += right;
return left;
}
// multiplication, heterogeneous types
X operator*(X left, float right)
{
left *= float;
return left;
}
X operator*(float left, const X & right)
{
return right * left;
}
Operator | Typical signature | Class member? | Notes |
---|---|---|---|
bitwise and (compound) | X & X::operator&=(const X & other) |
Should be member |
|
bitwise or (compound) | X & X::operator|=(const X & other) |
Should be member |
|
bitwise xor (compound) | X & X::operator^=(const X & other) |
Should be member |
|
bitwise left shift (compound) | X & X::operator<<=(std::size_t n) |
Should be member |
|
bitwise right shift (compound) | X & X::operator>>=(const X & other) |
Should be member |
|
bitwise and | X operator&(X left, const X & right) |
Should be non-member |
|
bitwise or | X operator|(X left, const X & right) |
Should be non-member |
|
bitwise xor | X operator^(X left, const X & right) |
Should be non-member |
|
bitwise left shift | X operator<<(X left, std::size_t n) |
Should be non-member |
|
bitwise right shift | Xoperator>>(X left, const X & right) |
Should be non-member |
|
bitwise not | X X::operator~() const |
Should be member |
|
// logical and
X operator&(X left, const X & right)
{
left &= right;
return left;
}
Operator | Typical signature | Class member? | Notes |
---|---|---|---|
logical and | bool operator&&(const X & left, const X & right) |
Should be non member |
|
logical or | bool operator||(const X & left, const X & right) |
Should be non member |
|
logical not | bool X::operator!() const |
Should be member |
|
Operator | Typical signature | Class member? | Notes |
---|---|---|---|
equality | bool operator==(const X & left, const X & right) |
Should be non member |
|
inequality | bool operator!=(const X & left, const X & right) |
Should be non member |
|
less-than | bool operator<(const X & left, const X & right) |
Should be non member |
|
less-or-equal-than | bool operator<=(const X & left, const X & right) |
Should be non member |
|
greater-than | bool operator>(const X & left, const X & right) |
Should be non member |
|
greater-than-or-equal | bool operator>=(const X & left, const X & right) |
Should be non member |
|
// inequality
bool operator!=(const X & left, const X & right)
{
return ! (left == right);
}
// less-or-equal-than
bool operator<=(const X & left, const X & right)
{
return ! (left > right);
}
// greater-than
bool operator>(const X & left, const X & right)
{
return right < left;
}
// greater-than-or-equal
bool operator>=(const X & left, const X & right)
{
return right <= left;
}
Operator | Typical signature | Class member? | Notes |
---|---|---|---|
pre-increment | X & X::operator++() |
Should be member |
|
post-increment | X X::operator++(int) |
Should be member |
|
pre-decrement | X & X::operator--() |
Should be member |
|
post-decrement | X X::operator--(int) |
Should be member |
|
// pre-increment
X & X::operator++()
{
// DO INCREMENT HERE...
return *this;
}
// post-increment
X X::operator++(int)
{
X old{*this};
++*this;
return old;
}
Operator | Typical signature | Class member? | Notes |
---|---|---|---|
stream extraction | std::ostream & operator<<(std::ostream & os, const X & x) |
Must not be member |
|
stream insertion | std::istream & operator>>(std::istream & is, X & x) |
Must not be member |
|
Operator | Typical signature | Class member? | Notes |
---|---|---|---|
function call | Z X::operator()(Y y) const |
Must be member |
|
comma | Y operator,(const X & left, const Y & right) |
Should be non-member |
|
conversion | X::operator Y() const |
May be member or not |
|
copy assignment | X & X::operator=(const X & other) |
Must be member |
|
move assignment | X & X:::operator=(const X && other) |
Must be member |
|
https://github.com/straykangaroo/cpp-operator-overloading-cheatsheet
© 2023 Costantino Astithas