P.J. Plauger is senior editor of C/C++ Users Journal. He is convener 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.
Introduction
The header <iomanip> presents several templates and a handful of manipulators that exploit them. It is the standardized version of the traditional iostream header <iomanip.h), which contained several clever macros. Those macros have been replaced by several clever templates, which take some careful describing.This is the first of several headers I intend to present, in my implementation of the Standard C++ library, that define templates. Until recently, it was also arguably the trickiest use of templates in the Standard C++ library. (The Standard Template Library beats it cold, however.) I begin, therefore, by sketching the history that led to the templates in <iomanip). I also include here a general discussion of templates, current template technology, and how I have chosen to implement templates used by the Standard C++ library.
Manipulators
Manipulators have lots of uses. The ones I've shown in recent columns let you alter the formatting information stored in an object based on class ios, extract white space, insert special characters, and flush output streams. And they let you do so in the guise of inserting or extracting a function designator with a reasonably mnemonic name. Perhaps the happiest example of all is:
cout << endl;which inserts a newline character to end the current text line and flushes the output to any waiting interactive display.For all their power, however, these inserters share a common limitation. You have no way to convey any additional, variable information. The name of the function says what it does, but that's also all it says. The limitation is intrinsic in the operator notation:
ostream& operator<<(ostream& os, X x);It is clear that you can pass only one additional argument to the left shift (insertion) operator, besides the necessary ostream& left argument. For a manipulator, that additional argument x of type X must convey the address of the function to call, as in (*x)(os). (X is presumably some pointer to function type, defined earlier in the translation unit.) There's hardly any room to convey additional arguments.Or is there? Remember, X can be an arbitrary class. You can construct a class that stores both a function pointer and an extra argument, as in:
class X { public: X(ostream& (*pf_arg)(ostream&, T), T val_arg) : pf(pf_arg), val(val_arg) {} friend ostream& operator<< (ostream&, X); private: ostream& (*pf)(ostream&&, T); T val; };Presumably, T is defined as a type earlier in the translation unit. You can then overload the insertion operator, as in:
ostream& operator<<(ostream& os, X x) { // assemble function // call and do it return ((*x.pf)(os, x.val)); }This inserter accepts an object of class X, digs out the pieces, and makes the desired function call. (It is a friend of the class so it can see the pieces.)To use this machinery, you have to supply a pair of functions:
The visible function assembles an object of class X. Inserting that object causes the hidden function to get called.
- the visible function, taking a single argument of type T, that you call to obtain the right operand of an insertion operator
- the hidden function, taking two arguments, that does the actual work
An Example Manipulator
Here's a realistic example. Say you want to insert an occasional integer in hexadecimal format, but you don't want to have to keep fiddling the format bits. After each such insertion, you want the integer base to revert to whatever it was before the hexadecimal insertion. To do so, you can write the function:
ostream& insert_hex(ostream& os, int val) { // insert a hex integer ios::fmtflags old = os.setf(ios::hex, ios::basefield); os << val; os.setf(old, ios::basefield); return (os); }You can then write calls such as insert_hex(cout, mask). But that doesn't play so nicely with the inserter notation. Better to make this the hidden function, using the machinery I showed above. The visible function might then overload the name hex, as in:
X hex(int val) { // save info to call insert_hex(val) return (X(&insert_hex, val)); }Now you can write something like:
cout << "mask is "<< hex(mask) << endl;The new hex assembles an object of class X which, when inserted, calls insert_hex with the stored argument. The net effect is that mask is inserted in hexadecimal format, regardless of the current settings for the basefield format flags, which are left unaltered.Neat, huh? Even neater is the fact that you can define manipulators that work just with inserters, as above, or just with extractors, or with both. In the last case, you probably want the left operand to be an object of the base class ios. A subobject of class ios captures what is common to objects of class ostream (the subject of inserters) and of class istream (the subject of extractors). You have lots of choices.
The problem is, you have too many choices. And you have too much tedious work to do for each choice you make. It's nuisance enough to have to write a pair of functions for each manipulator, but that's the most visible, problem-specific part. Just to prepare for writing this pair of functions, you need to define a class and overload one or two operators on that class. And you have to define a different class for each argument type you wish to pass, and for each object you wish to operate on (ios, istream, or ostream).
Generic Macros
The traditional approach to replicating chunks of code in C is to define macros to do it for you. In fact, typical implementations of the header <iostream.h> (note the .h suffix) contained some truly impressive macro definitions. Such generic macros let you write the hairy code once, then expand it on demand with a given type, or types, filled in at the critical places.The modern approach in C++ is to define templates instead. A template class defines an open-ended set of classes with one or more parameters. Typically, a parameter specifies a type, which you write as class T, regardless of whether or not T is truly a class. That's the special power of templates that removes one more excuse to write messy macros. A parameter can also supply a value, however, such as the number of bits in a set (as with the library class bits<N>, to be described later).
The class X I defined earlier, for example, converts easily to a template class. All you need to do is add a prefix to the class definition:
template<class T> class X { public: X(ostream& (*pf_arg)(ostream&, T), T val_arg) : pf(pf_arg), val(val_arg) {} friend ostream& operator<<(ostream&, X); private: ostream& (*pf)(ostream&, T); T val; }Now T has quite a different meaning. Before, you were obliged to define T as a type before writing the definition for class X. If you wanted the same shaped class for a different type T2, you had to replicate all the code and alter all the references to T. With a template class, however, T is a parameter name. Much like the name of a parameter to a function call, it has special meaning within, and only within, the body of the definition that follows.
Instantiating Templates
To make use of such a template class, you write an instantiation. If, for example, you want an object that stores an argument value of type int, you write its type as X<int>. Construct such an object and the translator generates on your behalf a version of the constructor that traffics in int second arguments. Declare another such object and the translator can probably recycle the special code for an int template parameter. Declare an object of type X<double>, however, and the translator will most likely generate a whole 'nother constructor just for a double template parameter.The point is, you can instantiate as many flavors of a given template class as your heart desires. The translator supplies whatever executable code that's necessary, on demand. If it can find clever ways to recycle existing code, so much the better for space efficiency. But in any event, you benefit from the coding convenience of writing something once and using it multiple times. And unlike macros, the translator can better check the sanity of template classes, both when they're defined and when they're instantiated.
You can also write template functions. Template parameters can supply types for the parameters of the function, as in:
template<class T> ostream& insert_hex(ostream& os, T val) { // insert a hex integer of type T ios:: fmtflags old = os. setf(ios::hex, ios::basefield); os << val; os.setf(old, ios::basefield); return (os); }A call to insert_hex<long>(cout, mask) instantiates a version of the function with a second parameter of type long.That's handy, but the machinery doesn't stop there. You can even write insert_hex(cout, (long)mask), or any call whose second argument is implicitly of type long, and the translator will instantiate the flavor insert_hex<long> for you(!) No need to spell out in advance the instantiations you expect to need. Just write the function call and leave it to the translator to guess which instantiation works best.
You can even count on a limited amount of argument type conversion, and back fitting, as part of the matching process. Here, for example, is the template function that accompanies our template class:
template<class T> ostream& operator<<(ostream& os, X<T> x) { // assemble function call and do it return ((*x.pf)(os, x.val)); }Inserting an X<int> does indeed instantiate the int version of this function.
Template Limitations
Such powerful notation is a two-edged sword. The translator is obliged to guess, from the argument types, what corresponding template parameters you must have intended. You are thus obliged to help the translator in some ways:
The Committee has eliminated some of these constraints, but current implementations still have them. You can also find differences of interpretation among implementations no surprise with any fairly new technology. So the bottom line is, don't ask too much of the machinery for automatically instantiating template functions.
- Every template function parameter must affect the type of one or more function parameters in some way, however else it is used in the function definition.
- Value parameters cannot be used as template function parameters, since they seldom can influence overload resolution.
Templates have other limitations as well, at least for now. Current implementations have fundamental differences in how they manage template definitions. You can, in principle, write a template class like any other. You define the template class itself in a header, but keep the member function definitions in a separate source file or files. In practice, however, not all implementations deal with this approach well, if at all. And some implementations have limited capacity for dealing with complex template definitions.
Writing inline definitions for template functions of all flavors seems to work most reliably in current implementations. Of course, you run risks in doing so, at least with certain compilers. A nontrivial inline function definition may still expand to inline code, so the compiler generates an excess of code. Other compilers choose not to inline code that contains loops, such as for or while statements. (The cfront preprocessor won't even translate such code. It favors a different template style.) Whether or not that is a good decision, the compilers still see fit to complain repeatedly about such code. I have had reasonable programs fail to compile simply because the compiler decided it had emitted too many (gratuitous) diagnostics.
The style I chose for this implementation is to write only inline definitions for all template functions. I try to keep them small, for a host of reasons. Occasionally, I offload some work to secret functions that are shared across template instantiations. But the basic risks of this approach remain, as I outlined above. Still, I find this approach best for the sake of portability and clear presentation today. You may need to alter the representation of some templates presented here, if for no other reason than to improve translation speed, execution speed, or generated code size.
Using the Header
You include the header <iomanip> for one of two reasons:
I showed how to use the predefined manipulators much earlier, in conjunction with the ios member functions that access and alter formatting information. (See "Standard C: The Header <ios>," CUJ May '94.) All are based on the template class smanip<T>, to work with both extractors and inserters. Very briefly:
- to use one or more of the predefined manipulators that take an argument
- to define one or more additional manipulators that take an argument
The manipulator notation usually results in more code generated and executed than for direct calls to the ios member functions. But unless you can demonstrate, in a particular case, that the cost is important, you should probably use manipulators wherever possible, if only for clarity.
- resetiosflags(fmtflags), clears the specified format flags
- setiosflags(fmtflags), sets the specified format flags
- setbase(int), sets the basefield format flags to match the specified numeric base
- setfill(int), alters the fill character
- setprecision(int), alters the floating-point precision
- setw(int), alters the field width
You can define your own manipulators as well, using the templates defined in <iomanip>. (See Listing 1 for usage examples.) The header defines three template classes:
Each template class also one or two associated template functions for performing insertions and extractions.
- smanip<T>, for use with both extractors and inserters
- imanip<T>, for use with extractors only
- omanip<T>, for use with inserters only
A few guidelines:
As always, I encourage you to find an existing manipulator that does something similar to what you want to do, then imitate its structure.
- Use these templates only when you have variable information to convey. Manipulators that take no arguments are much easier to write.
- Some objects don't suffer copying about. (Objects of class streambuf are an obvious example.) For these, pass a pointer instead, if the object does not always exist. Otherwise, pass a reference to the object.
- If you need to pass more than one value, define a class that holds all the needed values. Call the manipulator with a constructor for the composite value as its single argument value.
- Use class smanip<T) only for a manipulator that makes sense as both an inserter and an extractor, and only when the ios subobject is all you need to manipulate. Otherwise, use class imanip<T> or class omanip<T>, as appropriate.
Implementing <iomanip>
Listing 1 shows the file iomanip, which implements the standard header <iomanip>. All template functions appear within the header as inline definitions, as I discussed earlier. Note that the template classes have public (but secret) member objects, so the template functions need not be declared as friend functions.As you might guess, the macros _TRY _BEGIN, CATCH_ALL, and _CATCH_END encapsulate exception handling. How they expand depends on whether a given translator implements exceptions. The draft C++ Standard says that an exception thrown within a manipulator is supposed to set ios::failbit in the stream and continue. The implementation of these template classes and functions is otherwise as I outlined earlier.
The remaining code implements the predefined manipulators. All follow the same pattern, which I also outlined earlier. I present here just one representative example. Listing 2 shows the file setw.c, which defines the manipulator setw. Note how it defines (locally) the hidden function that the manipulator calls.