InformIT

Stream Manipulators and Iterators in C++

Date: May 28, 2004

Return to the article

In the first part of this series, we covered how to create user-defined extractors and inserters to help facilitate the process of reading, writing, storing, and retrieving user-defined objects. In this article, we discuss how defining manipulators for user-defined objects helps in the formatting of those objects when inserted or extracted from streams.

Now that you have learned (from our previous article in this series) how to create user-defined extractors and inserters to help facilitate the process of reading, writing, storing, and retrieving user-defined objects, let's take a look at how defining manipulators for user-defined objects helps in the formatting of those objects when inserted or extracted from streams.

To take full advantage of the stream processing model in C++ requires three basic steps:

  1. Decide how the user-defined class needs to be translated during stream insertion or extraction.

  2. Identify the stream states that are necessary for the proper translation of your user-defined class; then define manipulators and user-defined flags that will accommodate those stream states.

  3. Define the inserters and the extractors for the user-defined class.

Examples of stream states include numeric bases (hexadecimal, octal, etc.) for a stream of numbers, or whitespace removal or insertion of NULL for a stream of characters. For our user-defined course class, a stream state would be horn clauses or XML formatting. The manipulators are used by the inserters and extractors to determine how the user-defined objects are translated and formatted for the stream.

Built-in Manipulators

Manipulators are functions or function objects that are inserted into or extracted from a stream; they affect the formatting of the objects that are used with that stream, or affect the stream itself in some way. Manipulators are used to control and set the state of the stream.

Table 1 lists commonly used built-in manipulators.

Manipulator

Description

endl

Outputs a newline character and flushes the stream.

ends

Outputs a null character.

flush

Flushes a stream.

dec

Converts numeric values to decimal.

hex

Converts numeric values to hexadecimal.

oct

Converts numeric values to octal.

resetiosflags(value)

Turns off flags specified by value.

setbase(value)

Sets the number base to value.

setfill(value)

Sets the fill character to value.

setiosflags(value)

Turns on flags specified by value.

setprecision(value)

Sets the precision to value.

setw(value)

Sets the field width to value.


Manipulators are used directly in the stream; for instance, the setfill, setw, and endl manipulators used in Listing 1 determine how the value of Programming101.startTime() will be formatted in the stream.

Listing 1 Using the Built-in Manipulators setfill, setw, and endl with a User-Defined Object

int main(int argc, char *argv[])
{
  ...
  course Programming101;
  Programming101.startTime("1200");
  cout << setfill('*') << setw(7) << Programming101.startTime()
    << endl;
  ...
  return(0);
}

The setw() manipulator requires that the next value to be inserted into the stream have a minimum width of 7. The setfill manipulator requires that the asterisk (*) be used for any necessary padding. The endl manipulator inserts a newline (\n) character in the stream. The manipulators in Listing 1 collectively will cause the following to be inserted into the cout stream:

***1200

NOTE

In this case, the value is right-aligned by default. Left alignment could have been specified using the setiosflags manipulator.

Custom Manipulators for User-Defined Objects

The built-in manipulators go a long way, but we need more. In Listing 1, we used built-in manipulators to format a single attribute of our user-defined course object. While this is useful, we would also like to insert our entire course object into a stream and have it properly formatted. For instance, sometimes our course object needs to be represented as XML, sometimes HTML, sometimes as a horn clause. Further, various data members need to be represented differently depending on the stream. Our course start times and end times could be represented using military time, simple (wall clock) time, etc. While we could write special member functions for each of these activities, it would be nice to simply insert manipulators to do the job.

The advantage of using manipulators, inserters, and extractors over regular member functions for a class is that the stream-and-file metaphor are maintained. In the long run, this makes for code that's easier to read, maintain, and use. The supplier (writer) of a class has to do a little more work up front, but ultimately the supplier's job is made easier and the user of a class benefits immediately.

We want to specify our own manipulators to accommodate user-defined objects. There are two basic kinds of manipulators: those that don't take arguments, such as endl and ends; and those that do, such as setprecision() and setfill(). Manipulators that don't require arguments are easier to implement, taking the following general form:

Xstream &manipulator_name(Xstream &Stream)
{
  // user code
  // alter the stream in some way
  // set the stream state
  return(Stream);
}

where Xstream is either a member of the ostream family of classes or from the istream family of classes. For instance, if we wanted to define a manipulator that would change the state of the stream to format floating-point numbers with a decimal point and a precision of 2, we could define a manipulator with the name scientific and use it as shown in Listing 2:

Listing 2 Defining a Scientific Manipulator Using the Built-in Manipulators setiosflags and setprecision to Format a Floating-Point Number

#include <iostream>
#include <iomanip>

ostream &scientific(ostream &Out)
{
  Out << setiosflags(ios::showpoint | ios::scientific);
  Out << setprecision(2);
  return(Out)
}

int main(int argc,char *argv[])
{
  ...
  float Num = 3452.2334;
  //will insert 3.45e+03 into the stream
  cout << scientific << Num << endl;
  ...
}

Our scientific manipulator simply ends up being a shortcut for some of the built-in manipulators. We're not always so lucky.

The manipulators that take an argument require two basic functions: the first function defines the manipulator, and the second function is a call to a template that has been defined in iomanip. Which template to use depends on whether the user-defined manipulator will be used with istream, ostream, or a combination of the two:

Listing 3 defines a manipulator that requires an argument. The manipulator is used to insert spaces into the stream.

Listing 3 Defining a Manipulator That Requires the Number of Spaces To Insert into a Stream as the Argument

#include <fstream>
#include <iomanip>

ostream &setspaces(ostream &Out, int NumSpaces)
{
  int CurrentWidth;
  CurrentWidth = Out.width();
  Out << setw(NumSpaces) <<  " ";
  Out << setw(CurrentWidth);
  return(Out);
}

omanip<int> spaces(int NumSpaces)
{
  return omanip<int> (&setspaces, NumSpaces);
}

int main(int argc,char *argv[])
{
  ...
  course Course;
  cout << Course.startTime() << spaces(11) Course.endTime()
    << spaces(4) Course.description() << endl;
  ...
}

The setspaces() function in Listing 3 actually does the work of the manipulator, and the spaces() function is used as the name of the manipulator. Again, our spaces() manipulator is a simple shortcut for built-in manipulators; we're inserting our course object one data member at a time into the stream.

We want to insert our entire user-defined course object into the stream and have the appropriate representation used, as well as appropriate formatting for each data member. To do this, we want to use user-defined manipulators so that we can set the stream to a user-defined state. User-defined manipulators often require the use of user-defined format flags; the stream classes have a storage area for such flags. This storage is dynamically allocated for the stream classes. This storage area is accessed by the iword(), pword(), and xalloc() functions:

In our case, the user-defined state will determine whether course times are formatted using a military format or a simple (wall clock) format. The user-defined state will determine whether our course object should be formatted as horn clause, XML, or HTML. We use the iword() and xalloc() methods to set up a couple of user-defined state flags. One flag will be used to identify what time format should be used, the other will be used to identify whether horn clause, XML, or HTML will be used. Both the manipulator and the user-defined inserters and extractors will need access to the user-defined flags. There are a couple of easy ways to employ the user-defined format flags so that they can be accessible by the manipulators, inserters, and extractor:

We'll use the first technique here. Because every call to xalloc() returns a new index, we want to save that index in a container. The map class is useful because we can associate a flag name with each index:

map<string,long> UserFlag;

We'll set up five simple flags:

const long WallClockTime = 4;
const long MilitaryTime = 3;
const long HClause = 5;
const long HtmlRep = 6;
const long XmlRep = 7;

Each one represents a translation state for the stream. In Listing 4, we define two manipulators, one named militaryTime and the other clockTime, and set up our TimeFormat user-defined flag:

Listing 4 Defining Manipulators for Time Formatting and Setting Up a User-Defined Flag

ostream &militaryTime(ostream &SomeStream)
{
  if(UserFlag.find("TimeFormat") == UserFlag.end()){
   UserFlag["TimeFormat"] = SomeStream.xalloc();
  }
  SomeStream.iword(UserFlag["TimeFormat"]) = MilitaryTime ;
  return(SomeStream);
}


ostream &clockTime(ostream &SomeStream)
{
  if(UserFlag.find("TimeFormat") == UserFlag.end()){
   UserFlag["TimeFormat"] = SomeStream.xalloc();
  }
  SomeStream.iword(UserFlag["TimeFormat"]) = WallClockTime ;
  return(SomeStream);
}

These manipulators use the find() method to check whether TimeFormat has already been stored in the map. If not, xalloc() is called to get the next available index from SomeStream; that index is then used for the TimeFormat flag. Each manipulator in Listing 4 sets the value of the TimeFormat flag to the appropriate value:

User-Defined Inserters and Extractors

When these manipulators are inserted into an ostream object, they set the user-defined flags that can then be used by the user-defined inserter or extractor. Listing 5 demonstrates how a user-defined inserter would access user-defined flags:

Listing 5 Defining an Inserter That Accesses a User-Defined Flag When Formatting for Time

ostream &operator<<(ostream &Out,course &X)
{
  if(Out.iword(UserFlag["TimeFormat"]) == MilitaryTime){
   Out << X.StartTime.c_str();
  }
  if(Out.iword(UserFlag["TimeFormat"]) == WallClockTime){
   int SomeTime = atoi(X.StartTime.c_str());
    strstream Buffer;
    string NewTime;

    if(SomeTime <= 1200){
      Buffer << SomeTime << ends;
      Buffer >> NewTime;
     if(SomeTime >= 1000){
       NewTime.insert(2,":");
     }
     else{
        NewTime.insert(1,":");
     }
      Out << NewTime.c_str() << " a.m." << endl;
    }
    else{
        SomeTime = SomeTime - 1200;
        Buffer << SomeTime << ends;
        Buffer >> NewTime;
        NewTime.insert(1,":");
        Out << NewTime.c_str() << " p.m." << endl;
    }
   }
  return(Out);
}

The inserter defined in Listing 5 determines how to print out a course time depending on how the user-defined flags are set.

Listing 5 demonstrates how a single flag can be used with user-defined inserters. Because we've stored the flags in a map container, any number of user-defined flags can be accessed in the inserters and extractors to determine how the object should be translated, as shown in Listing 6.

Listing 6 Defining an Inserter That Utilizes the Map Container To Store User-Defined Flags

ostream &operator<<(ostream &Out,course &X)
{
  if(Out.iword(UserFlag["TimeFormat"]) == MilitaryTime){
  ...
  // military time processing
  ...
  }
  if(Out.iword(UserFlag["HornClause"]) == HClause){
   Out << toHornClause()  // Horn Clause processing
  }
  if(Out.iword(UserFlag[SomeUserDefinedFlag]) == FlagValue){
   // Specialized  processing
  }
  return(Out);
}

Once the inserters, extractors, and manipulators for the user-defined class have been written, we can combine our customized stream processing with the algorithms and containers. The ostream_iterator and istream_iterator class serves as the glue between the iostreams, algorithms, and container classes, as shown in Listing 7:

Listing 7 Using an ostream Iterator and the copy Algorithm To Send a Vector of User-Defined Objects to a Printer

...
int main(int argc,char *argv[])
{
  ...
  ofstream DegreePlan(open("/dev/lp0",O_WRONLY));
  vector<course> Schedule;
  course Course;
  ...
  DegreePlan << hornClause << clockTime;
  ostream_iterator<course> Out(DegreePlan,"\n");
  copy(Schedule.begin(),Schedule.end(),Out);
  ...
}

Listing 7 uses the copy algorithm to send our Schedule object to a printer. We've constructed an ofstream object and connected to a printer named /dev/lp0. We've set our user-defined flags with the hornClause and clockTime manipulators. The ostream_iterator is constructed with our ofstream object. We then can use the copy algorithm to send our course objects (represented as horn clauses) to the printer.

How does this work? The ostream_iterator and istream_iterators will ultimately cause the user-defined inserters and extractors to be called. That topic will be discussed in our next article.

800 East 96th Street, Indianapolis, Indiana 46240