Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Extending <tt>&lt;iostream&gt;</tt> to Support User-Defined Types

Last updated Jan 1, 2003.

Legacy applications and diehards still use C's <stdio.h> for performing I/O tasks. Yet, the library suffers from several limitations that make it unsuitable for use in object-oriented environments. For instance, you can't extend <stdio.h> to support user-defined types.

Unlike <stdio.h>, <iostream> classes and objects are extensible. In the following sections, I will you show how to extend <iostream> components by overloading operator << so that it can display a user-defined object.

Supporting Built-in and Standard Types

The most widely used objects of the <iostream> library are cin and cout, which perform console-oriented input and output, respectively. Note that cin and cout are real objects, not classes, which C++ instantiates before the program's outset.

These objects define several overloaded versions of the operators >> and << for data input and output. The overloaded versions support all the built-in types of C++ such as int, double and bool. In addition, they support various types of pointers: char * (i.e., C-strings), void * (for pointers in general) and some of the Standard Library's classes, including std::string and std::complex.

For this reason, the following program will run smoothly on any standard-compliant C++ implementation:

#include <iostream>
#include <string>
#include <complex>
using namespace std;

int main()
{
complex <double> comp(1.0, 0.0);
cout<<comp<<endl;
string s;
const char * msg="enter your name: ";
cout<<msg<<endl;
cin>>s;
bool b= true;
void *p = &b;
cout<<"b's value is: "<<b<<", b's address is: "<<p<<endl;
}

The output from this program should look more or less as follows:

<1,0>
enter your name: 
Danny
b's value is: true, b's address is: 0012ff3b

This is all well and good. Not only do cin and cout support a vast set of data types and standard classes, such as std::string, but you're free from the burden of using cumbersome format flags to indicate the type of an argument in a cout expression. However, suppose you declared the following class:

class student
{
private:
 string name;
 string department;
public:
 student(const string& n, const string& dep) 
 : name(n), department(dep) {}
 student() {} //default ctor

 string get_name() const { return name; }
 string get_department () const { return department; }
 void set_name(const string& n) { name=n; }
 void set_department (const string& d) {department=d;}
};

And you want to be able to use it in a cout statement as follows:

student st("James E.", "Biology");
cout<<st; // display student's details

The cout expression will cause a compilation error, because there's no overloaded version of << that supports student.

Overloading cout's <<

To extend cout's support for a user-defined type, you need to overload the operator << of class ostream (cout is an instance of ostream). The canonical form of such an overloaded << looks as follows:

ostream& operator << (ostream& os, const student& s);

In other words, an overloaded << returns a reference to an ostream object, and takes two parameters by reference: an ostream object and a user-defined type. The user-defined type is declared const parameter because the output operation doesn't modify it.

The body of the overloaded << inserts members of the user-defined object which cout does support into the ostream object. In this case, the two data members of student are of type std::string. As previously shown, cout natively supports this class:

os<<s.get_name()<<'\t'<<s.get_department()<<endl;

Make sure that the members inserted are separated by a tab, newline, or spaces. Otherwise, they will appear as if they were concatenated when displayed on the screen. Remember also to place the endl manipulator at the end of the insertion chain to force a buffer flush. Finally, the overloaded operator should return the ostream object into which the members have been inserted. This enables you to chain several objects in a single cout statement, like this:

student s1("Bill","CS"), s2("Jane", "Linguistics";
cout<<s1<<s2; // chaining multiple objects

The complete definition of the overloaded << operator is as follows:

ostream& operator << (ostream& os, const student& s)
{
 os<<s.get_name()<<'\t'<<s.get_department()<<endl;
 return os;
}

The insertion operations and the return statement can be accomplished in a single statement:

ostream& operator << (ostream& os, const student& s)
{
 return os<<s.get_name()<<'\t'<<s.get_department()<<endl;
}

Now you can use the overloaded << in your code:

int main()
{
student s1("Bill F.","CS"), s2("Jane E.", "Linguistics");
cout<<s1<<s2; // chaining multiple objects
}

As expected, this program displays:

Bill F.  CS
Jane E.  Linguistics

Overloading cin's <<

At this point you're probably wondering whether it's possible to extend cin as well, so that it can read an entire object from the standard input in a single cin expression. The answer is Yes. Although this technique is less commonly used, you can define an overloaded version of operator << that reads the values of student's data members from the standard input, like this:

istream& operator >> (istream& is, student & stu)
{
 string name, dep;
 cout<<"enter name: ";
 cin>>name;
 stu.set_name(name);
cout<<"enter department: ";
 cin>>dep;
 stu.set_department(dep);
 return is; //enable chaining
}
student s1, s2;
cin>>s1>>s2; //fill the objects
cout<<s1<<s2;

Summary

For trivial classes such as student, you don't really have to define a specialized version of operator <<. Instead, you can access its two data members through student's get_name() and get_department() or set_name() and set_department() in the case of operator <<. However, for more complex classes that have dozens of data members, it's advisable to overloaded << and >> and thus spare users the trouble of having to extract or insert every data member individually.