Introduction
I note that the Draft C++ Standard will provide a Standard Library which I presume will include the existing iostream classes. I do not know whether additional text formatting classes have been added but, just in case they have not, I have outlined the features of a class I have developed from ostream and its related classes.
The class
The class, which I have called Formatter, provides for a formatting string to specify how a sequence of object values is to be formatting as text. It is based on a similar facility which I developed in Ada 83 and presented at the Ada UK 93 Conference. The implementation in C++ is much simpler (~250 lines of C++) thanks to the support already provided by the ostream class hierarchy.
The class interprets a formatting string based on the conventions used by printf() in C, but applies them in a totally type-safe way, as described below.
Whilst the class provides little functionality that is not already supported by ostream it allows this functionality to be much more concisely, conveniently and obviously utilised (tabulated output is easy!). I know that most people, familiar with printf() in C, hanker after a similarly powerful and concise formatting notation for use in C++.
Well here it is!
The class has been compiled and used with CenterLine C++ on a Sparc. It no doubt could be refined and I believe a comparable class could be developed for reading formatted input.
Features and functionality of the class Formatter
-
Applies a formatting string, in the style used by C's printf(), to a sequence of object values.
-
The formatting is completely type-safe because the object values control the interpretation of the formatting string.
-
The formatting string is repeatedly interpreted for as long as values, to be formatted, are applied to it.
-
A default formatting string is supported which automatically separates each formatted value by a single space
-
The class operations can be applied to an ostream object, or an object of any class derived from ostream.
-
The class supports all the format control facilities of ostream in a form that is easier and more concise to use.
-
The class supports features that are not supported by ostream, e.g. controlling the case of alphabetic characters.
-
The class uses operator << () for notational consistency with ostream.
-
The class saves and restores the ostream format properties so that it does not interfere with the formatting behaviour of ostream objects or vice versa.
-
The default formatting of the class can be overridden on a per-object basis.
-
A client class T can provide a "friend Formatter& operator << (Formatter&, T&)" operation so that its textual representation may be formatted.
-
Operations on an ostream object and a Formatter object may be freely intermixed, in a type-safe way, within a single expression.
-
The interpretation of the formatting string supports substring repetition (a la ALGOL 68).
-
It should not be possible for an object of the class Formatter to be misused (its constructors, destructor, new() and delete() operations are private; perhaps assignment and comparison should also be private?).
The significant features of the Formatter class are shown in the abbreviated class declaration that follows:
// -------- Formatter class declaration ----------- #include <iostream.h> #include <strstream.h> class Formatter { ostream& dest; // other private data members Formatter () : dest(cout) {}; Formatter (const Formatter&) :dest(cout) {}; Formatter (ostream & os, char *form); ~Formatter (); void * operator new (size_t) {return NULL;}; void operator delete (void*, size_t) {}; // other private methods public: friend Formatter operator % (ostream& os, char *form); // convert an ostream into a Formatter Formatter& operator % (char *defaults) ; // change the default formats Formatter& operator << (ostrstream *); // format the text from a memory buffer Formatter& operator << (int); // convert basic data values Formatter& operator << (int *); // into formatted text Formatter& operator << (float); Formatter& operator << (char); Formatter& operator << (char *); // other member functions that correspond to ostream::operator <<() member functions // manipulators that convert a Formatter object back into the ostream object // from which it was constructed friend ostream& endf (Formatter&); // output to end of formatting string friend ostream& stopf (Formatter&); // stop use of formatting string friend ostream& endl (Formatter&); // as endf + output of newline friend ostream& stopl (Formatter&); // as stopf + output of newline ostream& operator << (ostream& (*f) (Formatter&)) {return (*f)(*this);} static char one_space[2]; }; #define SP Formatter::one_space // default formatting string of a single space separator // --end of Formatter class declaration --------------------------
Translation of values into textual representation
Each '%' character in the formatting string acts as a placeholder in the generated text for the value which will replace it (unless immediately followed by another '%' when it represents itself). The characters following a '%' character constitute a formatting control pattern which determines how the value will be translated and formatted. A format control pattern is always terminated by a character which determines the mode of translation that is to be applied to a value, as defined below.
Table 1.
Type of value | Mode character | Interpretation |
---|---|---|
Integer | s (default) | decimal value |
d | decimal value | |
o | octal value | |
O | octal value with leading 0 | |
x | hexadecimal value | |
X | hexadecimal value with leading 0x | |
float | s (default) | 6 decimal digits |
e | scientific | |
E | scientific in upper case | |
f | fixed | |
g | full precision format | |
G | full precision format in upper case | |
string (char *) | s (default) | leave string as is |
U | all alphabetic characters in upper case | |
L | all alphabetic characters in lower case | |
m | all alphabetic characters in European case (leading and after a '_' in upper case; others in lower case) |
When the translation mode character does not correspond to the type of the value to be translated the default translation is applied to the value.
Controlling the layout of values
The user can control the layout of the generated text by specifying:
-
the field width for a value
-
the justification of a value within its field
-
the precision of scientific and fixed values
-
the character to be used to pad a translated value to a specified field width
These layout controls must appear between the '%' character and the translation mode character in the format: '%WJPFs' where 's' in one of the above translation mode characters. The allowable representation for WPJF is summarised below:
Table 2.
Layout Control | Value | Interpretation |
---|---|---|
width: W | integer | the field width |
+integer | he field width and prefix integer values with their sign default is W == '0' => minimal width for value | |
justification: J | . | default justification (used to separate W from P) |
> | right justify | |
< | left justify | |
precision P | integer | number of digits after decimal point default is P = '6' |
fill F | _c | use character 'c' to pad the field default is to pad with spaces |
The default values of W, J, P and F may be changed on a per-object basis by the application of the "Formatter& Formatter::operator % (char *defaults)" operation.
In this context the parameter "defaults" is a formatting string where each format control pattern is interpreted as setting the defaults for the type of value specified by its translation mode character; any other characters are ignored.
Repeated substrings
Alternatively, %W may be followed by a substring enclosed by '{' and '}'. to specify that the substring is to be interpreted W times. For example, the format string:
"abc%s %4{xy%16ez} %x"
is equivalent to:
"abc%s xy%16ezxy%16ezxy%16ezxy%16ez %x"
Examples of modus operandi
Given the object declarations:
int i = 123; float f = 4567.89; char s[] = "Format this";
and the following format strings:
static char ft1[] = "Formatted: %d %s %f %U <<\n"; static char ft2[] = "%s||"; static char ft3[] = "%15_,s";
the following C++ expression will produce the text shown to their right:
cout % ft1 << i << i << f << s << endf; Formatted: 123 123 4567.890137 FORMAT THIS? << cout % ft1 << i << f << s << i << endf; Formatted: 123 4567.89 Format this? 123 << cout % ft2 << s << f << i << endl; Format this||4567.89||123|| cout % ft2 << s << f << i << stopl; Format this||4567.89||123 cout % SP << f << i << s; 4567.89 123 Format this cout % ft3 << f << i << s; ,,,,,,,,4567.89,,,,,,,,,,,,123,,,,Format this
The C++ code:
Formatter& fmt = cout % "Prefix: %2{%7s: }Suffix: %5s:\n"; int i = 123; for (int j = 0; j < 100; j += 10 ) fmt << i+j; for (int k = 100; k < 2000; k += 200 ) fmt << i+k;
will produce:
Prefix: 123: 133: Suffix: 143: Prefix: 153: 163: Suffix: 173: Prefix: 183: 193: Suffix: 203: Prefix: 213: 223: Suffix: 423: Prefix: 623: 823: Suffix: 1023: Prefix: 1223: 1423: Suffix: 1623: Prefix: 1823: 2023