P.J. Plauger is senior editor of C/C++ Users Journal. He is convenor of the ISO C standards committee, WG14, and active on the C++ committee, WG21. His latest books are The Draft Standard C++ Library, and Programming on Purpose (three volumes), all published by Prentice-Hall. You can reach him at pjp@plauger.com.
Two Asides
Before I continue where I left off last month, I have two asides. The first is that I finally delivered my latest book to the publisher. By the time you read this, bookstores should have copies of The Draft Standard C++ Library, published by Prentice Hall. It describes a complete implementation of the Standard C++ library, at least as it stood earlier this year.You've already seen bits and pieces of that code, and the explanations that go with it, in earlier installments of this column. The last dozen or so "Standard C/C++" columns served as useful input to the book. Now the process is reversed. This and subsequent columns are derived from the book.
No, they are not simply direct copyings. The requirements of a magazine article are seldom exactly the same as for a book chapter. I endeavor each month to provide a bite-size sampler of the Standard C++ library, drawing on material in the book to make the discussion as rich and informative as possible. (Long-time readers of this magazine may recall that I performed much the same exercise with my earlier opus, The Standard C Library.)
The second aside concerns the status of the draft C++ Standard. I mentioned in last month's Editor's Forum that the draft is now being prepared for a ballot within ISO leading to registration as a Committee Draft. What this means is that major additions should stop popping up in future editions of the C++ Standard. Of course, a heckuva lot of stuff got added to the library at the eleventh hour. You can be sure that changes will occur on a regular basis until all this new stuff gets debugged and some of the older stuff changes to match.
I don't intend to try to track all these changes on a regular basis. Any explanations I endeavor to provide here would only confuse after a while. Instead, I'm going to keep bulling ahead describing the code I've implemented. That's not as reactionary as it sounds. Many of the additions to the Standard C++ library involve "templatizing" existing iostreams and string classes. The required instantiations of these templates for type char, with rare exceptions, have the same behavior as what I describe here.
There's a good reason for that. My implementation of the Standard C++ library caught it at a time when it had codified all the existing practice the Committee intended to standardize, and little more. The additions since then are extremely inventive. Some of them can't even compile with existing commercial compilers. Some have never been implemented. Others can be sort of compiled, by dummying out new language features. They then suffer from the limitations of current template technology, which often gags or bogs down when templates are used extensively.
C++ lets you write a specialization for a template. Rather than rely on an important instantiation to expand rapidly into efficient code, you can preempt template processing with your own version. I suspect that a typical implementation of the Standard C++ library will do exactly this for the traditional flavor of iostreams that is used so heavily. The code I present here, with a few small fiddles, provides just the specialization you might expect from a sensible implementation.
I certainly intend to keep you informed about what's happening with the draft C++ Standard, particularly the library portion. But fast-breaking news has to stay decoupled from the ongoing explanation of an implementation that works with today's technology. With that in mind, we return to our regularly scheduled program.
Introduction
This is the second installment on the class ostream, defined in the header <ostream> (See "Standard C: The Header <ostream>," CUJ, September 1994.) Last month, I showed the definition of the class and described the member functions that perform unformatted output. Figure 1 shows how ostream fits into the class structure of iostreams. I continue this month, as promised, with a description of the member functions that perform formatted output. These functions are commonly known as inserters.For convenient reference, I once again provide a description of class ostream. Listing 1 shows how the draft C++ Standard defines it. You might want to look it over now, then refer back to it as needed in the discussion that follows.
Inserters overload operator<< for a left parameter of type ostream&. That lets you write code such as:
float fl; cout << fl;which converts the float value fl to a character sequence and inserts those characters into the stream controlled by cout (the standard output stream). The character sequence is essentially the same as that produced by the Standard C library function fprintf, declared in <stdio.h>. The entire sequence must be successfully inserted in the stream buffer for the function to succeed. Otherwise, the function reports failure, typically by setting badbit in cout.Thus, inserters serve the same function for the Standard C++ library that the fprintf family serves for the Standard C library. You can, in fact, implement inserters by calls to fprintf. That turns out to be not always convenient, however:
- The destination for output from a stream controlled by an ostream object is controlled in turn by a streambuf object. You can't always relate that destination directly to the destinations writeable by fprintf or sprintf.
- You can store characters into a buffer using sprintf then insert them into the stream buffer. But doing so requires a buffer that is arbitrarily large. Or you end up doing so much postprocessing of the text that you duplicate much of the work done by sprintf.
- The program can specify an arbitrary fill character for an inserter, not just the implied space character (' ') supplied by fprintf. Moreover, the program can specify that this arbitrary character be used for "internal" fill, in place of the implied zero ('0') supplied by fprintf.
- For these reasons, you can't count on calls to fprintf to do all the hard work of implementing inserters. Nevertheless, the definition of a typical inserter member function in ostream is in terms of fprintf conversion specifiers. When in doubt, you can often turn to the C Standard for a more precise description of inserter behavior.
Kinds of Inserters
Last month, I discussed the inserters that perform a minimum of interpretation. These insert one or more characters from the output stream and deliver them either to memory or to an output stream. The simplest is operator<<(char&), which inserts a single character. As usual, class ostream also has unsigned char and signed char versions of the same inserter. The member function operator<<(const char *) inserts a sequence of characters into the output stream from a null-terminated string in the character array designated by the pointer argument. Yes, there are three flavors, once again. And yes, you can pad a string on either end with fill characters.The remaining formatted output functions are my principal topic this month. They insert various scalar types. For all of these inserters, you can specify a field width, a fill character, and the format flags in the group adjustfield. These work together to determine whether and where to insert fill characters as padding when generating the text representation of a value. The scalar inserters further subdivide into integer types, floating-point types, and pointer to void. You control the behavior of each of these groups with different subsets of the format flags.
You can, of course, also write your own inserters. It is commonplace, when designing a new class, to provide a tailored inserter at the very least. If reading values of the class makes sense, then it is good manners to provide an extractor as well. You might even want to write an inserter or two that are not associated with a specific class.
The best style for writing new inserters is to do so in terms of the member functions of class ostream. If you must drop below this level and access the associated streambuf object directly, then by all means match the discipline followed in some existing inserter. (That assumes, of course, that you can get your hands on such source code.) Extractors are more fragile than inserters, because of the need to look ahead in the input sequence and push back an occasional character. But inserters are delicate enough in their own right. Don't take unnecessary chances.
How Inserters Work
The scalar inserters in class ostream are important work horses. Each converts some encoded form within a C++ program to a human-readable text sequence (or field). The form is determined by type T in a member declaration of the form:
ostream& operator<<(T x);where x designates the value of the argument expression.In all cases, the following steps occur, in order:
If tie() is not a null pointer, the function flushes the specified stream.
- If either of the status bits badbit or failbit is set, the function makes no attempt to insert characters.
The function generates the minimum-length character sequence that represents the argument value, as determined by the argument type and any relevant format flags.
In all cases, the function returns *this, so that inserters can be chained.
- If the field width is greater than the length of this character sequence, the function pads the character sequence with fill characters as determined by the format flags in the adjustfield group.
- If the function cannot insert all the characters in the generated field, it sets badbit.
- If the format flag unitbuf is set, the function flushes the output stream.
Integer Inserters
Integer inserters exist for the types char, signed char, unsigned char, short, int, unsigned int, long, and unsigned long. You cannot insert an integer value directly from an expression of a character type. An inserter for a character type inserts the character code instead, as I described earlier. You must type cast the character expression ch to some integer type, as with (int)ch.Remember that the basefield format flags determine the base for both input and output conversions. For inserters only, clearing all basefield flags calls for decimal output.
For all the integer types, you can control:
- the numeric base for the text representation by how you set the ios::basefield group of format flags
- whether non-negative values have a leading +, by setting the format flag showpos
- whether octal values have a leading 0 or hexadecimal values have a prefix 0x or 0X, by setting the format flag showbase
- whether hexadecimal values use uppercase letters for x and the digits a through f, by setting the format flag uppercase
- whether padding occurs after any sign and/or prefix and before the digits, by setting just the format flag internal in the group adjustfield
Floating-Point Inserters
Floating-point inserters exist for the types float, double, and long double. They generate the same formats as the fprintf conversion specifiers e, f, and g (or their uppercase versions). For niggling details, see a precise description of that function (such as in P&B92).For all the floating-point types, you can control:
Specifically, the three formats selected by the group floatfield follow the same rules as for the fprintf conversion specifiers e (scientific), f (fixed), and g (any other).
- whether non-negative values have a leading +, by setting the format flag showpos
- whether to preserve the decimal point along with any trailing zeros, by setting the format flag showbase
- whether to use an uppercase letter for e, by setting the format flag uppercase
- whether padding occurs after any sign and before the digits, by setting just the format flag internal in the group adjustfield
- whether to include a decimal exponent, by setting just the format flag scientific, or to omit a decimal exponent, by setting just the format flag fixed, or to choose the format that best represents the value of the number, by setting any other combination of flags in the group floatfield
Pointer Inserter
You can use the inserter operator<<(void *) to convert an arbitrary object pointer. The draft C++ Standard defines this conversion in terms of the p conversion specifier for fprintf, but the C Standard has little to add on that topic. Certainly, whatever this inserter generates should be acceptable to its corresponding istream extractor, and should recover the same value. But little is promised about how pointers appear as character sequences.Often, an implementation displays a pointer to void as one or more hexadecimal integer values. And often, the conversion is affected by the same flags that qualify hexadecimal conversions. But the draft C++ Standard is mum on this topic. Don't count on much in the way of portable behavior when inserting pointers. (I use such inserters only for debugging.) And make a point of writing an explicit (void *) type cast before any pointer you wish to insert. A pointer to char will certainly get hijacked by another inserter. And you never know when someone else might decide to write an inserter that accepts some other object pointer type.
User-Defined Inserters
The remaining member function in class ostream is the sole "support function" (unlike class istream, which has several). You call it when you want to take more direct control of the associated stream buffer, as when defining your own inserters or your own manipulators.You call the member function flush() simply to synchronize the output stream with any external file. I discussed it and related operations last month.
Generating output is a much simpler task than parsing input, but I still counsel you to keep such antics to a minimum. The member functions I have described so far also represent a considerable amount of engineering in their own way, just like extractors. You are more likely to violate some subtle semantic constraints if you strike off on your own. So wherever possible, I urge you to write new inserters and manipulators in terms of existing ones.
An alternative way to write your own inserters is to model them after an existing one. Follow the pattern I showed last month:
try { if (opfx()) <perform any output> osfx(); } catch (...) { setstate(badbit); throw; }I also gave some guidelines for calling the public member functions of class streambuf several months ago. (See "Standard C: The Header <streambuf>," CUJ, June 1994.)
Locales
As a final note, I remind you that the ostream member functions exhibit some behavior that depends upon the current locale:
If your program alters the current locale by calling setlocale, declared in <locale.h>, the behavior of some inserters can thus change. Moreover, future versions of iostreams may behave differently.
- A "decimal point" in a floating-point conversion is the character localeconv( )->decimal_point[0], where localeconv is declared in <locale.h>.
- Floating-point inserters may generate alternate formats outside the "C" locale.
The Committee has already approved the addition of "locale objects," which encapsulate all aspects of a C-style locale. You "imbue" an ios object with its own locale object. Even if you don't however, the object no longer tracks the current locale. I doubt that many people are concerned about such matters yet. (Many will never be.) Just know that change is in the works in this rather mysterious area.