ACCU Home page ACCU Conference Page ACCU 2017 Conference Registration Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Google+ ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinA Text Formatting Class

Overload Journal #6 - Mar 1995 + Programming Topics   Author: John Smart

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.

<colgroup> <col> <col> <col></colgroup> <thead> </thead> <tbody> </tbody>
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.

<colgroup> <col> <col> <col></colgroup> <thead> </thead> <tbody> </tbody>
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

Overload Journal #6 - Mar 1995 + Programming Topics