straykangaroo / cpp-operator-overloading-cheatsheet

C++ operator overloading cheatsheet

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

C++ Operator Overloading Cheatsheet

Table of contents

  1. Introduction
  2. Brief recap
  3. Conventions
  4. Operators
    1. Object access operators
    2. Arithmetic operators
    3. Bitwise operators
    4. Boolean (logical) operators
    5. Comparison (relational) operators
    6. Increment / Decrement operators
    7. I/O streams operators
    8. Other operators
  5. Contact
  6. Copyright notice

Introduction

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.

Brief recap: what is operator overloading?

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;
}

Conventions used in this guide

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

Operators

Object access operators

OperatorTypical signatureClass member?Notes
array subscript (non-const) T & C::operator[](std::size_t idx) Must be member
  • The parameter may be std::size_t or whatever makes sense (see: associative containers)
  • Multiple overloads are allowed
  • If T is a built-in type, return by value
  • Since C++23 may have multiple parameters
array subscript (const) const T & C::operator[](std::size_t idx) const Must be member
  • The parameter may be std::size_t or whatever makes sense (see: associative containers)
  • Multiple overloads are allowed
  • If T is a built-in type, return by value
  • Since C++23 may have multiple paramters
array subscript (for pointer like objects such as random access iterators) I I::operator[](std::size_t idx) const Must be member
  • Equivalent to *this + idx (may implement as such)
  • I should be cheap to copy
dereference (non-const) Y & X::operator*() Should be member
dereference const Y & X::operator*() const Should be member
  • If Y is a built-in type, return by value
arrow (non-const) Y * X::operator->() Must be member
  • Must return a pointer or a proxy object (overloading operator->() itself). Note that chaining occurs
arrow (const) const Y * X::operator->() const Must be member
  • Must return a pointer or a proxy object (overloading operator->() itself). Note that chaining occurs
pointer to member Y & C::operator->*(X x) May be member or not
address-of Y * X::operator&() Should be member

Arithmetic operators

OperatorTypical signatureClass member?Notes
addition (compound) X & X::operator+=(const X & other) Should be member
  • Return *this
  • Parameter may be the same type or whatever makes sense, but beware conversions
  • Multiple overloads are allowed
subtraction (compound) X & X::operator-=(const X & other) Should be member
  • Return *this
  • Parameter may be the same type or whatever makes sense, but beware conversions
  • Multiple overloads are allowed
multiplication (compound) X & X::operator*=(const X & other) Should be member
  • Return *this
  • Parameter may be the same type or whatever makes sense, but beware conversions
  • Multiple overloads are allowed
division (compound) X & X::operator/=(const X & other) Should be member
  • Return *this
  • Parameter may be the same type or whatever makes sense, but beware conversions
  • Multiple overloads are allowed
modulus (compound) X & X:operator%=(const X & other) Should be member
  • Return *this
  • Parameter may be the same type or whatever makes sense, but beware conversions
  • Multiple overloads are allowed
addition X operator+(X left, const X & right) Should be non-member
  • Implement in terms of operator+=
  • Note that left is passed by value
  • left and right may be of heterogeneous types (beware symmetry issues)
  • May return something other than X
subtraction X operator-(X left, const X & right) Should be non-member
  • Implement in terms of operator-=
  • Note that left is passed by value
  • left and right may be of heterogeneous types (beware symmetry issues)
  • May return something other than X
multiplication X operator*(X left, const X & right) Should be non-member
  • Implement in terms of operator*=
  • Note that left is passed by value
  • left and right may be of heterogeneous types (beware symmetry issues)
  • May return something other than X
division X operator/(X left, const X & right) Should be non-member
  • Implement in terms of operator/=
  • Note that left is passed by value
  • left and right may be of heterogeneous types (beware symmetry issues)
  • May return something other than X
modulus X operator%(X left, const X & right) Should be non-member
  • Implement in terms of operator%=
  • Note that left is passed by value
  • left and right may be of heterogeneous types (beware symmetry issues)
  • May return something other than X
unary minus X X::operator-() const Should be member
unary plus X X::operator+() const Should be member

Examples

// addition
X operator+(X left, const X &amp; 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;
}

Bitwise operators

OperatorTypical signatureClass member?Notes
bitwise and (compound) X & X::operator&=(const X & other) Should be member
  • Return *this
  • Parameter may be the same type or whatever makes sense, but beware conversions
  • Multiple overloads are allowed
bitwise or (compound) X & X::operator|=(const X & other) Should be member
  • Return *this
  • Parameter may be the same type or whatever makes sense, but beware conversions
  • Multiple overloads are allowed
bitwise xor (compound) X & X::operator^=(const X & other) Should be member
  • Return *this
  • Parametre may be the same type or whatever makes sense, but beware conversions
  • Multiple overloads are allowed
bitwise left shift (compound) X & X::operator<<=(std::size_t n) Should be member
  • Return *this
  • Parameter is usually std::size_t or whatever makes sense, but beware conversions
  • Multiple overloads are allowed
bitwise right shift (compound) X & X::operator>>=(const X & other) Should be member
  • Return *this
  • Parameter is usually std::size_t or whatever makes sense, but beware conversions
  • Multiple overloads are allowed
bitwise and X operator&(X left, const X & right) Should be non-member
  • Implement in terms of operator&=
  • Note that left is passed by value
  • left and right may be of heterogeneous types (beware symmetry issues)
  • May return something other than X
bitwise or X operator|(X left, const X & right) Should be non-member
  • Implement in terms of operator|=
  • Note that left is passed by value
  • left and right may be of heterogeneous types (beware symmetry issues)
  • May return something other than X
bitwise xor X operator^(X left, const X & right) Should be non-member
  • Implement in terms of operator^=
  • Note that left is passed by value
  • left and right may be of heterogeneous types (beware symmetry issues)
  • May return something other than X
bitwise left shift X operator<<(X left, std::size_t n) Should be non-member
  • Implement in terms of operator<<=
  • Note that left is passed by value
  • Parameter n is usually std::size_t or whatever makes sense, but beware conversions
bitwise right shift Xoperator>>(X left, const X & right) Should be non-member
  • Implement in terms of operator>>=
  • Note that left is passed by value
  • Parameter n is usually std::size_t or whatever makes sense, but beware conversions
bitwise not X X::operator~() const Should be member

Examples

// logical and
X operator&(X left, const X & right)
{
    left &= right;
    return left;
}

Boolean (logical) operators

OperatorTypical signatureClass member?Notes
logical and bool operator&&(const X & left, const X & right) Should be non member
  • If overloaded will not have short circuit semantics
  • May also return X or some other type
  • Until C++17 no sequence point holds
logical or bool operator||(const X & left, const X & right) Should be non member
  • If overloaded will not have short circuit semantics
  • May also return X or some other type
  • Until C++17 no sequence point holds
logical not bool X::operator!() const Should be member
  • May also return X or some other type

Comparison (relational) operators

OperatorTypical signatureClass member?Notes
equality bool operator==(const X & left, const X & right) Should be non member
  • May also compare to other types, if it makes sense, but beware conversion and symmetry issues
inequality bool operator!=(const X & left, const X & right) Should be non member
  • May also compare to other types, if it makes sense, but beware conversion and symmetry issues
  • Implement in terms of operator==
less-than bool operator<(const X & left, const X & right) Should be non member
  • May also compare to other types, if it makes sense, but beware conversion and symmetry issues
less-or-equal-than bool operator<=(const X & left, const X & right) Should be non member
  • May also compare to other types, if it makes sense, but beware conversion and symmetry issues
  • Implement in terms of operator>
greater-than bool operator>(const X & left, const X & right) Should be non member
  • May also compare to other types, if it makes sense, but beware conversion and symmetry issues
  • Implement in terms of operator<
greater-than-or-equal bool operator>=(const X & left, const X & right) Should be non member
  • May also compare to other types, if it makes sense, but beware conversion and symmetry issues
  • Implement in terms of operator<=

Examples

// 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;
}

Increment / Decrement operators

OperatorTypical signatureClass member?Notes
pre-increment X & X::operator++() Should be member
  • Return *this
post-increment X X::operator++(int) Should be member
  • Dummy int parameter is required
  • Return "old" *this
  • Implement in terms of operator++()
pre-decrement X & X::operator--() Should be member
  • Return *this
post-decrement X X::operator--(int) Should be member
  • Dummy int parameter is required
  • Return "old" *this
  • Implement in terms of operator--()

Examples

// pre-increment
X & X::operator++()
{
    // DO INCREMENT HERE...
    return *this;
}

// post-increment
X X::operator++(int)
{
    X old{*this};
    ++*this;
    return old;
}

I/O streams operators

OperatorTypical signatureClass member?Notes
stream extraction std::ostream & operator<<(std::ostream & os, const X & x) Must not be member
  • Should return os
  • Restore stream state if modified
stream insertion std::istream & operator>>(std::istream & is, X & x) Must not be member
  • Should return is
  • Set stream state if errors

Other operators

OperatorTypical signatureClass member?Notes
function call Z X::operator()(Y y) const Must be member
  • May be const, or not
  • Return type and (multiple) parameters as needed
  • Multiple overloads are allowed
  • Tangentially related: function objects should be cheap to copy
comma Y operator,(const X & left, const Y & right) Should be non-member
  • May return whatever makes sense
  • Beware: no sequence point holds, so operands may be evaluated in any order
conversion X::operator Y() const May be member or not
  • Since C++ 11 may be marked explicit
  • Return type will be Y
copy assignment X & X::operator=(const X & other) Must be member
  • Return *this
  • Should free the resources held by *this
  • Should make a deep copy of the resources held by other
  • Might be defaulted or deleted (since C++11)
move assignment X & X:::operator=(const X && other) Must be member
  • Return *this
  • Should free the resources held by *this
  • Should "steal" the resources held by other and pass them to *this
  • Should leave other in a "null-like" but destructible state
  • Might be defaulted or deleted (since C++11)
  • Should be noexcept

Contact

https://github.com/straykangaroo/cpp-operator-overloading-cheatsheet

Copyright notice

© 2023 Costantino Astithas

About

C++ operator overloading cheatsheet