Pages

Saturday, April 29, 2017

Formatting

I/O streams formatting is achieved by more than one framework class working together. At the core are controlling flags, but there are other attributes that can't be described/signaled by a flag, such as the (desired) width of a certain I/O field and how to pad it as necessary. Nevertheless, I'll concentrate on the flags as they form the core.

One the second ISO C++ 11 standard (ISO/IEC 14882-2011(E)), section 27.5.3.1.2, we have the description of the streams formatting flags type, std::ios_base::fmtflags, which builds on a bitmask type (section 17.5.2.13), which is an enumerated type (section 7.2), which is a compound type (section 3.9.2), and so on...

Among other things, this means that, as a bitmask, it has most (if not all) the well-defined bit operations one might expect. But the problem is that this not speak aloud as it should be, which may cause some confusion or obfuscation, resulting in much less than optimal utilization of the feature. This is one is just one out of several other similar situation appearing to establish a mild kind of obscurantism that do not help in getting the most out of the streams framework, which is very nice, flexible and powerful.

So the formatting flags are just a bunch of bits each of which signaling a certain formatting behavior on a corresponding stream. Although defined on a "primarily" base type, those bits could be better perceived as a formatting state attribute of each stream instance. The bits (which I do not repeat the well-known meanings) are (alphabetically, per column):
 
boolalpha      left           showpoint
dec           
oct            showpos
fixed         
right          skipws
hex           
scientific     unitbuf
internal      
showbase       uppercase


As a kind of state, they can be "queried", "saved", "set" and "reset" and that's the point on getting the most out of them. Unfortunately, it's not very clearly stated how to make a "master reset" (revert to defaults), but from inspecting the fragments of documentation and examples excerpts one could infer that the default seems to be an "all clear", that is, all bits set to 0 (zero), or yet, more precisely, the value std::ios_base::fmtflags(0). But that's not actually the case! On the standard, table 128, among other things, the effects of std::basic_ios::init() sets up the following:

skipws | dec

Another point to note is that some of these bits are grouped according to mutually exclusive functionality bits. This "grouping" (called a field) is nothing more than referring to them as a whole by means of a predefined (bit) mask which ORes each bit value that is to comprise the mask. The masks are:
 
adjustfield    meaning     left | right | internal
basefield     
meaning     dec  | oct   | hex
floatfield    
meaning     scientific   | fixed

The manipulation of these flags can be grouped into 2 main scenarios:
  • Directly, by calling a stream member function;
  • Indirectly, by using manipulators on a "flow statement".

The second scenario above is by far the most usual as it's more intuitive and "right-to-the-point". But due to the intrinsic behavior of the formatting flags in the stream framework, the first scenario is crucial to internationalization, house-keeping, clean-coding and reassuring expected and bug-free behavior.

The member functions for the first scenario (defined in std::ios_base, section 27.5.3) are:
 
fmtflags flags  () const;
fmtflags
flags  (
fmtflags fmtfl ); // Replaces ALL

fmtflags
setf   (
fmtflags fmtfl ); // Sets SOME 
fmtflags
setf   (
fmtflags fmtfl, fmtflags mask );

void     unsetf (
fmtflags mask );  // Clears MASK

In the above declarations, strictly speaking, fmtfl in fact is also a mask of flags to be set. It's important to note how each of these member functions does one or more of the following operations (and in what order):
  • Replaces all flags (with "mask" of flags)
  • Sets some flags (adds a "mask" of flags)
  • Clears (unsets) the flags in a mask (but may keep some)

In particular, the second (overloaded) setf() starts by unconditionally clearing the flags comprising mask and then sets the flags in fmtfl but only if they correspond to some position in mask. This effectively means that the fmtfl "mask" may include some flags to be kept from clearing after all.

The stream manipulators for the second scenario have differentiated implementations for the case when they belong to a group (field) of mutually exclusive members, in that enabling one bit member automatically disables all other bit members. Those bits that don't belong to any group (field) have both a set and a corresponding unset (no) manipulator. Here are they all:

(no)boolalpha    (no)showpos    (no)uppercase       (no)showbase     (no)skipws                      (no)showpoint    (no)unitbuf                  

Typically implemented as:
  boolalpha  translating to     setf( boolalpha )
noboolalpha  translating to   unsetf( boolalpha )
...


And the group (field) related ones:
adjustfield  basefield  floatfield
left         dec        scientific
right        oct        fixed
internal     hex        hexfloat
( scientific | fixed )

Typically implemented as:

right       
translating to     setf( right, adjustfield )
...

Interestingly, there is one extra manipulator for handling the basefield. It takes 1 (one) parameter, and the problem is that an invalid parameter sets all bits in the group (field) to 0 (zero) which may be unexpected. It is the:

setbase       ()

Furthermore, there are two more manipulators supposed to more freely manage (set or clear) any flags (individually or combined on any "mask") independently of any grouping. They are:

setiosflags   ()
resetiosflags ()

One other important point to note is that the formatting flags do not control some formatting details such as width, filling (padding) and float number precision to be used. The filler, for instance, is dependent on the character traits. The member functions that deal with each of these extra formatting options appear in std::basic_ios which is derived from std::ios_base. They (those extra formatting options) also get initial defaults set by std::basic_ios::init() as following:

width         ()    defaults to   0
precision     ()   
defaults to   6
fill          ()   
defaults to   widen( ' ' )

And there are manipulators (taking 1 parameter) for each of the above member functions as well:

setw          ()
setprecision  ()
setfill       ()

For completeness, there's just another member function:
std::basic_ios::copyfmt()
This function is very handy in more advanced scenarios for copying the complete set of formatting related options between streams. The more advanced settings are those returned related to tie(), exceptions() and getloc(). The more obvious options are, of course, the ones discussed on this post:

flags         ()
width         ()
precision     ()
fill          ()

And there newer manipulators for money (get_money/put_money) and time (get_time/put_time) I won't cover now, as they are relatively more complex for this introduction.

To finish this story I'll show the bit values of the formatting flags under the implementation of GCC 4.8.2 under Solaris 11.3 (X86):

default flags: 0x00001002

    boolalpha: 0x00000001
          dec: 0x00000002
        fixed: 0x00000004
          hex: 0x00000008
     internal: 0x00000010
         left: 0x00000020
          oct: 0x00000040
        right: 0x00000080
   scientific: 0x00000100
     showbase: 0x00000200
    showpoint: 0x00000400
      showpos: 0x00000800
       skipws: 0x00001000
      unitbuf: 0x00002000
    uppercase: 0x00004000

  adjustfield: 0x000000B0 = internal | left | right
    basefield: 0x0000004A = dec | hex | oct
   floatfield: 0x00000104 = fixed | scientific


By the way, in my implementation, assuming the defaults, the lines:
std::cout.unsetf( std::ios_base::dec )
std::cout << 11 << std::end;
will output:
B