Session 3: Overloading

Polymorphism

Code that works for many types.

subtype polymorphism
(dynamic binding) - week 7

The version executed is determined dynamically. (Savitch 14,15; Stroustrup 12; Horstmann 14)

ad-hoc polymorphism
(overloading) - this week

The version executed is determined statically from the types of the arguments (Savitch 8.1; Stroustrup 7.4,11; Horstmann 13.4)

parametric polymorphism
(genericity) - next week

A single version, parameterized by types, is used (Savitch 16.1-2; Stroustrup 13.2-3; Horstmann 13.5)

Overloading

A single symbol has multiple meanings. The meaning of a particular use is statically determined by the types of its arguments.

The following may be overloaded in C++:

Implicit conversions and overloading

Ambiguity

Given the definitions
    void f(int i, double y) { ... }
    void f(double x, int j) { ... }
the following is rejected by the the compiler:
    f(1, 2);      // ambiguous!
We could get around this by also defining
    void f(int i, int j) { ... }
Then every application would have a best match.

Overloaded equality

In C++, we can compare values of built-in types:
    int i;
    if (i == 3) ...
We can also compare objects:
    string s1, s2;
    if (s1 == s2) ...
and similarly for vectors.

The == operator is overloaded: special definitions have been given for string, vector and many other types.

Expanding overloaded operators

An operator can be either an independent function or a member function, in each case with a special name starting with operator:

Binary operators
An expression a == b could mean either of

Unary operators
An expression ! a could mean either of
As with ordinary overloading, there must be a unique best match.

Comparing points

    class Point {
        int _x, _y;
    public:
        Point(int x, int y) : _x(x), _y(y) { }
        int x() const { return _x; }
        int y() const { return _y; }

        bool operator==(const Point &p) const {
            return _x == p.x() && _y == p.y();
        }
    };

An alternative definition

We could instead have defined an independent function:
    bool operator==(const Point &p1, const Point &p2) {
        return p1.x() == p2.x() && p1.y() == p2.y();
    }
In either case we can then write
    Point p1, p2;
    ...
    if (p1 == p2)
       ...
    if (p1 == Point(0, 0))   // temporary object
       ...

A note on types

Other comparison operators

The <utility> header file (which is included by <string>, <vector> and other data types) defines So usually we need only define == and <, but we can also define the others if required.

Operators available for overloading

Only built-in operators can be overloaded:

 unary ~ ! + - & * ++ --  
 binary + - * / % ^ & | << >>  
   += -= *= /= %= ^= &= |= <<= >>=  
   == != < > <= >= && ||  
   = , ->* -> () []  
Their precedence and associativity can't be changed, so the expressions
        a + b + c * d           (a + b) + (c * d)
are always equivalent, no matter how the operators are overloaded.

Output of built-in types

Consider

        cout << "Total = " << sum << '\n';
This is equivalent to
        ((cout << "Total = ") << sum) << '\n';

The « operator

The ostream class

class ostream {
public:
        ostream& operator<<(char c);
        ostream& operator<<(unsigned char c);
        ostream& operator<<(int n);
        ostream& operator<<(unsigned int n);
        ostream& operator<<(long n);
        ostream& operator<<(float n);
        ostream& operator<<(double n);
        ...
};
In the string header file:
ostream& operator<<(ostream &out, const string &s);

Output of a user-defined type

    class Point {
        int _x, _y;
    public:
        Point(x, y) : _x(x), _y(y) { }
        int x() const { return _x; }
        int y() const { return _y; }
    };
The output operator for Points is defined as a non-member function:
    ostream& operator<<(ostream &s, Point p) {
        return s << '(' << p.x() << ", " << p.y() << ')';
    }

Using various versions of the « operator

Suppose we have an expression a « b, where a has type A, and b has type B. Then the relevant definition of « could be either

For example the following uses a mixture of these:
        Point p(2,3);
        cout << "The point is " << p << '\n';

On accessing private state

An accidental consequence of the way operators are defined in C++:

Input of built-in types

Input is almost the mirror image of output:

        int x, y, z;
        cout << "Please type three numbers: ";
        cin >> x >> y >> z;

The istream class

    class istream : virtual public ios {
    public:
            istream& operator>>(char &c);
            istream& operator>>(unsigned char &c);
            istream& operator>>(int &n);
            istream& operator>>(unsigned int &n);
            istream& operator>>(long &n);
            istream& operator>>(float &n);
            istream& operator>>(double &n);
            ...
    };
In the string header file:
    istream& operator>>(istream &in, string &s);

The state of an istream

The following methods of istream test its state:

bool eof();
the end of the input has been seen.
bool fail();
the last operation failed.
bool good();
the next operation might succeed.
(Equivalent to ! eof() && ! fail().)
bool bad();
the stream has been corrupted: data has been lost (read but not stored in an argument).
(Implies fail(), but not vice-versa.)
A test   if (s)    is equivalent to   if (! s.fail())

Input of a user-defined type

istream& operator>>(istream &s, Point &p) {
    int x, y;
    char lpar, comma, rpar;

    if (s >> lpar) {
        if ((s >> x >> comma >> y >> rpar) &&
            (lpar == '(' && comma == ',' && rpar == ')'))
            p = Point(x, y);
        else
            s.set(ios::badbit); // read failed
    }
    return s;
}

Next week