Operator Overloading

Motivation

Returning back to the venerable class point,

class point
{
public:
    point(int x, int y) : _x{x}, _y{y} {}

    int x() const { return _x; }
    int y() const { return _y; }

    void move(point vec);

private:
    int _x;
    int _y;
};

Equality of two points? I don’t want to have another function (or static method) like,

if (points_equal(p1, p2))
    ...

Enter operators

I want to write something like

if (p1 == p2)
    ...

What else?

  • Arithmetic operators: 2D space os full of arithmetic, so why not write for example …

    point vec{2,3}
    p += vec;
    
  • Stream operators: this is rather clumsy,

    std::cout << '(' << p.x() << ',' << p.y() << ')' << std::endl;
    

    I want to shift a point out,

    std::cout << p << std::endl;
    

Implementing (In)Equality

  • Operators are ordinary functions

  • Only named a bit oddly: operator==()

  • Most operators can be defined in two way

    • As global function (equality would then take two point parameters)

    • As object method: “hey left point, are you equal to this other point?”

Implementing (In)Equality: Global Function

  • Global function: a third party authority (global function, not belonging to a class) examines two points for equality

    • Makes use of overloading: there might be other == operators defined for different types

    • No access to private members of operands (could use the friend keyword though)

  • Makes sense to define equality and inequality together

  • ⟶ one can be implemented in terms of the other

#include <gtest/gtest.h>

class point
{
public:
    point(int x, int y) : _x{x}, _y{y} {}

    int x() const { return _x; }
    int y() const { return _y; }

private:
    int _x;
    int _y;
};

static inline bool operator==(point lhs, point rhs) // <--- global function (not inside class definition) ("static inline" is another story)
{
    return
        lhs.x() == rhs.x() &&                       // <--- using public access methods
        lhs.y() == rhs.y();                         // <--- using public access methods
}

static inline bool operator!=(point lhs, point rhs) // <--- global function (not inside class definition) ("static inline" is another story)
{
    return !operator==(lhs, rhs);                   // <--- defined in terms of "=="
}

TEST(operators_suite, equals_global)
{
    point p1{2, 3};
    point p2{3, 4};

    ASSERT_NE(p1, p2);
    ASSERT_EQ(p1, p1);

    // operators are ordinary functions:
    ASSERT_EQ(operator==(p1, p2), false);
    ASSERT_EQ(operator!=(p1, p2), true);
}

Implementing (In)Equality: Object Method

  • As a matter of taste: why not ask a point object if it is equal to another point object

  • Preferred by most: does not have the difficulties from above

  • Usage no different between both ways

  • Equality check does not normally modify objects ⟶ const

#include <gtest/gtest.h>

class point
{
public:
    point(int x, int y) : _x{x}, _y{y} {}

    int x() const { return _x; }
    int y() const { return _y; }

    bool operator==(point rhs) const     // <--- object method (const!)
    {
        return
            _x == rhs._x &&              // <--- direct member access possible
            _y == rhs._y;                // <--- direct member access possible
    }

    bool operator!=(point rhs) const     // <--- object method (const!)
    {
        return !operator==(rhs);         // <--- again, defined in terms of "=="
    }

private:
    int _x;
    int _y;
};

TEST(operators_suite, equals_object)
{
    point p1{2, 3};
    point p2{3, 4};

    ASSERT_NE(p1, p2);
    ASSERT_EQ(p1, p1);

    // operators are ordinary methods:
    ASSERT_EQ(p1.operator==(p2), false);
    ASSERT_EQ(p1.operator!=(p2), true);
}

Implementing Arithmetic: + (Vector Addition)

  • As with operator==(), there are two ways

    • Global function operator+(point lhs, point rsh)

    • Preferred: object method operator+(point rhs) const

Implementing Arithmetic: + (Vector Addition): Global Function

#include <gtest/gtest.h>

class point
{
public:
    point(int x, int y) : _x{x}, _y{y} {}

    int x() const { return _x; }
    int y() const { return _y; }

private:
    int _x;
    int _y;
};

point operator+(point lhs, point rhs) // <--- global function (not inside class definition) ("static inline" is another story)
{
    int x = lhs.x() + rhs.x();        // <--- using public access methods
    int y = lhs.y() + rhs.y();        // <--- using public access methods
    
    return point{x, y};
}

TEST(operators_suite, vector_addition_global)
{
    point p1{2, 3};
    point p2{3, 4};

    point sum = p1 + p2;

    ASSERT_EQ(sum.x(), 5);
    ASSERT_EQ(sum.y(), 7);
}

Implementing Arithmetic: + (Vector Addition): Object Method

#include <gtest/gtest.h>

class point
{
public:
    point(int x, int y) : _x{x}, _y{y} {}

    int x() const { return _x; }
    int y() const { return _y; }

    point operator+(point rhs) const     // <--- object method (const!)
    {
        int x = _x + rhs._x;             // <--- direct member access possible
        int y = _y + rhs._y;             // <--- direct member access possible
    
        return point{x, y};
    }

private:
    int _x;
    int _y;
};

TEST(operators_suite, vector_addition_object)
{
    point p1{2, 3};
    point p2{3, 4};

    point sum = p1 + p2;

    ASSERT_EQ(sum.x(), 5);
    ASSERT_EQ(sum.y(), 7);
}

Implementing Arithmetic: += (Moving A Point)

  • += can only be an object method (it is there to modify an object, alas)

  • Things are little confusing otherwise

  • ⟶ Beginning in C, += has a value

    #include <gtest/gtest.h>
    
    class point
    {
    public:
        point(int x, int y) : _x{x}, _y{y} {}
    
        int x() const { return _x; }
        int y() const { return _y; }
    
        point operator+(point rhs) const     // <--- object method (const!)
        {
            int x = _x + rhs._x;             // <--- direct member access possible
            int y = _y + rhs._y;             // <--- direct member access possible
        
            return point{x, y};
        }
    
    private:
        int _x;
        int _y;
    };
    
    TEST(operators_suite, vector_addition_object)
    {
        point p1{2, 3};
        point p2{3, 4};
    
        point sum = p1 + p2;
    
        ASSERT_EQ(sum.x(), 5);
        ASSERT_EQ(sum.y(), 7);
    }
    
  • When overloading operator+=(), usually (a reference to) the modified object is the value

    • Could be a copy just as well ⟶ possibly expensive

  • *this 🤔

#include <gtest/gtest.h>

class point
{
public:
    point(int x, int y) : _x{x}, _y{y} {}

    int x() const { return _x; }
    int y() const { return _y; }

    point& operator+=(point rhs)          // <--- object method (non-const, obviously)
    {
        _x += rhs._x;
        _y += rhs._y;
    
        return *this;
    }

private:
    int _x;
    int _y;
};

TEST(operators_suite, point_plus_equal)
{
    point p{2, 3};
    point vec{1, 2};

    point result = p += vec;

    ASSERT_EQ(p.x(), 3);
    ASSERT_EQ(p.y(), 5);

    ASSERT_EQ(p.x(), result.x());
    ASSERT_EQ(p.y(), result.y());
}

Implementing ostream Shift: std::cout << ...

  • Make use of overloading again

  • Overload (global) std::ostream& operator<<(std::ostream&, point)

#include <iostream>

class point
{
public:
    point(int x, int y) : _x{x}, _y{y} {}

    int x() const { return _x; }
    int y() const { return _y; }

private:
    int _x;
    int _y;
};

static std::ostream& operator<<(std::ostream& s, point p) // <--- "static inline" is another story
{
    s << '(' << p.x() << ',' << p.y() << ')';
    return s;
}


int main()
{
    point p1{1,2};
    point p2{3,4};

    std::cout << "p1: " << p1 << ", p2: " << p2 << std::endl;

    return 0;
}
$ ./c++03-ostream-shift-operator
p1: (1,2), p2: (3,4)