Example: Printing a Vector

Introduction

C++ vectors don’t work with << by default

vector<double> temps{1, 2, 3};

cout << temp; // compiler error: << doesn't know
              // how to print a vector

What We Want

lets print vectors using the C++ literal notation

vector<double> temps{1, 2, 3};

cout << temp; // prints "{1, 2, 3}"

The print Function

lets not worry about << and first just write a print function

void print(const vector<double>& v) {
    // ...
}

// ...

vector<double> v = {1, 2, 3}

print(v);  // "{1, 2, 3}"

The print Function

lets think about the basic cases

the empty vector is “{}”

a vector with 1 element has no commas, e.g. “{5}”

a vector with n elements has n - 1 commas, e.g. {1, 2, 3}

The print Function

this suggests we structure print like this

void print(const vector<double>& v) {
    int n = v.size();
    if (n == 0) {
        // ...
    } else if (n == 1) {
        // ...
    } else {
        // ...
    }
}

The print Function

the first two cases are easy

void print(const vector<double>& v) {
    int n = v.size();
    if (n == 0) {
        cout << "{}";
    } else if (n == 1) {
        cout << '{' << v[0] << '}';
    } else {
        // ...
    }
}

The print Function

for the general case it helps to look at a concrete example

the braces are easy, so lets not write those

1, 2, 3

3 numbers, 2 commas

“, ” before all the numbers but the first

The print Function

1, 2, 3

print the first number

for the rest of the numbers, print “, ” followed by the number

The print Function

we’re done (the first version)!

void print(const vector<double>& v) {
    int n = v.size();
    if (n == 0) {
        cout << "{}";
    } else if (n == 1) {
        cout << '{' << v[0] << '}';
    } else {
        cout << '{' << v[0];
        for(int i = 1; i < n; ++i) {
            cout << ", " << v[i];
        }
        cout << '}';
    }
}

// ...

vector<double> v = {1, 2, 3};
print(v);

The println Function

it’s convenient to also write a println function

void println(const vector<double>& v) {
    print(v);
    cout << '\n';
}

A Small Change to print

the n = 1 case is handled by the general “else” case

void print(const vector<double>& v) {
    int n = v.size();
    if (n == 0) {
        cout << "{}";
    //} else if (n == 1) {             //  delete these
    //    cout << '{' << v[0] << '}';  //  two lines
    } else {
        cout << '{' << v[0];
        for(int i = 1; i < n; ++i) {
            cout << ", " << v[i];
        }
        cout << '}';
    }
}

this simplifies and shortens print

A Problem

print only works with vector<double>

vector<string> intro = {"Once", "upon", "a", "time"};

print(intro);  // compiler error: print only works with
               //                 with vector<double>,
               //                 but intro is vector<string>

A Mediocre Solution

we could just write a version of print that takes a vector<double>

void print(const vector<string>& v) {
    int n = v.size();
    if (n == 0) {
        cout << "{}";
    } else {
        cout << '{' << v[0];
        for(int i = 1; i < n; ++i) {
            cout << ", " << v[i];
        }
        cout << '}';
    }
}

the only change here is in the header

vector<double> is now vector<string>

everything else is the same

A Mediocre Solution

this works

vector<string> intro = {"Once", "upon", "a", "time"};

print(intro);  // "{Once, upon, a, time}"

but you’ll need to do that for every type T that you have a vector<T> of

A Better Solution: Templates

C++ templates let you essentially pass types as parameters to functions (and classes)

template<class T>
void print(const vector<T>& v) {
    // ... same as before ...
}

template<class T>
void println(const vector<T>& v) {
    print(v);
    cout << '\n';
}

this works with any vector

now we only need to write one copy of the print function

A Note on Templates

templates turn out to be one of the most powerful and useful features of C++

many recent language decisions are based on using templates in new ways

basic uses of templates (like print) are straightforward, but there are many details that arise in other cases that make them more complex

error messages involving templates are often long and very hard to understand

Using the operator<<

C++ lets us overload the << operator

ostream& operator<<(ostream& out, const vector<double>& v) {
    // ...
    return out;
}

the name is operator<<

ostream is an output stream (such as cout)

note the return type is ostream& to avoid making a copy

the header must always have this same general structure

Using the operator<<

the body of operator<< is nearly the same as print

the differences are to use out instead of cout and it must end with return out

ostream& operator<<(ostream& out, const vector<double>& v) {
    int n = v.size();
    if (n == 0) {
        out << "{}";
    } else {
        out << '{' << v[0];
        for(int i = 1; i < n; ++i) {
            out << ", " << v[i];
        }
        out << '}';
    }
    return out;
}

Using the operator<<

now we can easily print vector<double>s

vector<double> v = {1, 2, 3};
cout << "v = " << v << endl;

Using the operator<<

finally, we replace vector<double> with vector<T> template

template<class T>
ostream& operator<<(ostream& out, const vector<T>& v) {
    int n = v.size();
    if (n == 0) {
        out << "{}";
    } else {
        out << '{' << v[0];
        for(int i = 1; i < n; ++i) {
            out << ", " << v[i];
        }
        out << '}';
    }
    return out;
}

Using the operator<<

vector<double> v = {1, 2, 3};
vector<string> intro = {"Once", "upon", "a", "time"};

cout << "    v = " << v
     << "intro = " << intro
     << endl;