Writing Your Own Stream Manipulators

Problem

You need a stream manipulator that does something the standard ones can't. Or, you want to have a single manipulator set several flags on the stream instead of calling a set of manipulators each time you want a particular format.

Solution

To write a manipulator that doesn't take an argument (à la left), write a function that takes an ios_base parameter and sets stream flags on it. If you need a manipulator that takes an argument, see the discussion a little later. Example 10-4 shows how to write a manipulator that doesn't take an argument.

Example 10-4. A simple stream manipulator

#include 
#include 
#include 

using namespace std;

// make floating-point output look normal
inline ios_base& floatnormal(ios_base& io) {
 io.setf(0, ios_base::floatfield);
 return(io);
}
int main( ) {

 ios_base::fmtflags flags = // Save old flags
 cout.flags( );

 double pi = 22.0/7.0;

 cout << "pi = " << scientific // Scientific mode
 << pi * 1000 << '
';

 cout << "pi = " << floatnormal
 << pi << '
';

 cout.flags(flags);
}

 

Discussion

There are two kinds of manipulators: those that accept arguments and those that don't. Manipulators that take no arguments are easy to write. All you have to do is write a function that accepts a stream parameter, does something to it (sets a flag or changes a setting), and returns it. Writing a manipulator that takes one or more arguments is more complicated because you need to create additional classes and functions that operate behind the scenes. Since argument-less manipulators are simple, let's start with those.

After reading Recipe 10.1, you may have realized that there are three floating-point formats and only two manipulators for choosing the format. The default format doesn't have a manipulator; you have to set a flag on the stream to get back to the default format, like this:

myiostr.setf(0, ios_base::floatfield);

But for consistency and convenience, you may want to add your own manipulator that does the same thing. That's what Example 10-4 does. The floatnormal manipulator sets the appropriate stream flag to output floating-point data in the default format.

The compiler knows what to do with your new function because the standard library already defines an operator for basic_ostream (basic_ostream is the name of the class template that ostream and wostream are instantiations of) like this:

basic_ostream& operator<<
(basic_ostream& (* pf)(basic_ostream&))

In this example, pf is a pointer to a function that takes a basic_ostream reference argument and returns a basic_ostream reference. This operator just calls your function with the current stream as an argument.

Writing manipulators that take arguments is more complicated. To understand why, consider how a manipulator without arguments works. When you use a manipulator like this:

myostream << myManip << "foo";

You use it without parenthesis, so that it actually resolves to the address of your manipulator function. operator<< is what actually calls the manipulator function, and it passes in the stream so the manipulator can do its work.

For the sake of comparison, say you have a manipulator that takes a numeric argument, so that, ideally, you would use it like this:

myostream << myFancyManip(17) << "apple";

How is this going to work? If you assume myFancyManip is a function that takes an integer argument, then there is a problem: How do you pass the stream to the function without including in the parameters and using it explicitly? Here's what you might do:

myostream << myFancyManip(17, myostream) << "apple";

But this is ugly and redundant. One of the conveniences of a manipulator is the ability to just add it in line with a bunch of operator<<s and to read and use it easily.

The solution is to send the compiler on a detour. Instead of operator<< just invoking your manipulator function on the stream, you need to introduce an ephemeral object that returns something operator<< can use. Here's how.

First, you need to define a temporary class to do the work. For the sake of simplicity, say you want to write a manipulator called setWidth that does the same thing as setw. The temporary structure you need to build should look something like this:

class WidthSetter {

public:
 WidthSetter (int n) : width_(n) {}
 void operator( )(ostream& os) const {os.width(width_);}
private:
 int width_;
};

The function of this class is simple. Construct it with an integer argument, and when operator( ) is invoked with a stream argument, set the width on the stream to the value that the object was initialized with. The point of this behavior is that WidthSetter will be constructed by one function and used by another. Your manipulator function is what will construct it, and it should look like this:

WidthSetter setWidth(int n) {
 return(WidthSetter(n)); // Return the initialized object
}

All this does is return a WidthSetter object that was initialized with the integer value. This is the manipulator that you will use in line with operator<<s, like this:

myostream << setWidth(20) << "banana";

But this alone is not enough, because if setWidth just returns a WidthSetter object, operator<< won't know what to do with it. You have to overload operator<< so it knows how to handle a WidthSetter:

ostream& operator<<(ostream& os, const WidthSetter& ws) {
 ws(os); // Pass the stream to the ws object
 return(os); // to do the real work
}

That solves the problem, but in a nongeneric way. You don't want to have to write a WidthSetter-style class for every argument-accepting manipulator you write (maybe you do, but never mind that), so a better approach is to use templates and function pointers to make a nice, generic infrastructure on which you can base any number of manipulators. Example 10-5 provides the ManipInfra class and a version of operator<< that uses template arguments to deal with the different kinds of characters a stream may handle and the different kinds of arguments a stream manipulator might use.

Example 10-5. Manipulator infrastructure

#include 
#include 

using namespace std;

// ManipInfra is a small, intermediary class that serves as a utility
// for custom manipulators with arguments. Call its constructor with a
// function pointer and a value from your main manipulator function.
// The function pointer should be a helper function that does the
// actual work. See examples below.
template
class ManipInfra {

public:
 ManipInfra (basic_ostream& (*pFun)
 (basic_ostream&, T), T val)
 : manipFun_(pFun), val_(val) {}
 void operator( )(basic_ostream& os) const
 {manipFun_(os, val_);} // Invoke the function pointer with the
private: // stream and value
 T val_;
 basic_ostream& (*manipFun_)
 (basic_ostream&, T);
};

template
basic_ostream& operator<<(basic_ostream& os,
 const ManipInfra& manip) {
 manip(os);
 return(os);
}

// Helper function that is ultimately called by the ManipInfra class
ostream& setTheWidth(ostream& os, int n) {
 os.width(n);
 return(os);
}

// Manipulator function itself. This is what is used by client
// code
ManipInfra setWidth(int n) {
 return(ManipInfra(setTheWidth, n));
}

// Another helper that takes a char argument
ostream& setTheFillChar(ostream& os, char c) {
 os.fill(c);
 return(os);
}

ManipInfra setFill(char c) {
 return(ManipInfra(setTheFillChar, c));
}

int main( ) {

 cout << setFill('-')
 << setWidth(10) << right << "Proust
";
}

If the sequence of events is still hazy, I suggest running Example 10-5 in the debugger. Once you see it in action, it will make perfect sense.

Building C++ Applications

Code Organization

Numbers

Strings and Text

Dates and Times

Managing Data with Containers

Algorithms

Classes

Exceptions and Safety

Streams and Files

Science and Mathematics

Multithreading

Internationalization

XML

Miscellaneous

Index



C++ Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2006
Pages: 241

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net