For example, consider the following output:
(I could argue that it has "unexpected" behavior; disregard the "[" and "]")
Formatting flags ... // OK
*******Hello, there! // OK; better if left aligned
Number 1: .........e // OK
Number 2: c^^^ // "Should be" [12^^]
Number 3: 14 // "Should be" [20]
Floating 1: 8.33^^^^^^ // "Should be" [ 8.33]
Floating 2: 1.83 // "Should be" [ 1.83]
Floating 3: 3.33^^^^^^ // "Should be" [ 3.333]
Going home...^^ // "Should be" [**Going home...]
I mean "should be" assuming that:
- member function calls would permanently change the formatting state
- manipulators calls would temporarily override the formatting state
But as you can see that's not the case and there's no even a "congruent misbehavior". Some member functions and manipulators (fill(), hex, left) permanently change behavior, others (width(), setw()) temporarily override, yet others misbehave strangely as precision (which should affect only how many decimal places to use and not the integral part as well!). In essence each situation is very simple, but when mixed on code, things can become quickly strange, perhaps a nightmare. A programmer can become quickly stressed and choose a less than optimal quick way out! But that's bad in the long run; that's not quality programming. Even the most simple programs such as rc-03 or the one listed below (which generated the above output) can face such annoyances when trying to print formatted output.
The lines of code that generated the above output are:
#include <cstdlib>
#include <iostream>
#include <iomanip>
void inner()
{
std::cout
<< "Number 1: "
<< std::setw( 10 )
<< std::setfill( '.' )
<< std::hex
<< 14
<< std::endl;
std::cout.fill( '^' );
std::cout
<< "Number 2: "
<< std::setw( 4 )
<< std::left
<< 12
<< std::endl;
std::cout
<< "Number 3: "
<< 20
<< std::endl;
std::cout
<< std::endl;
std::cout
<< "Floating 1: "
<< std::setw( 10 )
<< std::setprecision( 3 )
<< ( 25.0 / 3 )
<< std::endl;
std::cout.precision( 4 );
std::cout.width( 6 );
std::cout
<< "Floating 2: "
<< std::setprecision( 3 )
<< ( 11.0 / 6 )
<< std::endl;
std::cout
<< "Floating 3: "
<< std::setw( 10 )
<< ( 10.0 / 3 )
<< std::endl;
}
void outer()
{
std::cout.fill( '*' );
std::cout
<< std::setw( 20 )
<< "Hello, there!"
<< std::endl
<< std::endl;
inner();
std::cout
<< std::endl
<< std::setw( 15 )
<< "Going home..."
<< std::endl;
}
int main()
{
std::cout
<< std::endl
<< "Formatting issues ..."
<< std::endl
<< std::endl;
outer();
return EXIT_SUCCESS;
}
This is the problem!
The definite solution would be to fix the framework.
But that I know is not gonna happen soon.
Perhaps on the next decade :-D
So I (we) have to live with that and cope with the problem. But instead of "hard-coding" a lot of "sets" and "unsets" at each I/O flow statement it would be nice to have an easy, light and quick amendment. My personal solution was to devise a lightweight class to help manage the formatting state of a given stream derived from std::basic_ios<>. The class wraps a std::stack<> which elements are a structure of the most common (as above) formatting options. Upon construction and destruction the class will set the given stream to its default formatting state. Furthermore, push() and pop() operations are published to help saving and reseting the more recent default or custom state. Here's the code (header "toolbox.h"):
#ifndef TOOLBOX_H
#define TOOLBOX_H
#include <iostream>
#include <vector>
#include <stack>
namespace TB
{
template< typename T >
struct fmt
{
explicit fmt( std::basic_ios< T > & ios ) : ios( ios )
{
init();
}
~fmt()
{
init();
}
void push()
{
context.push
(
{
ios.flags(),
ios.precision(),
ios.width(),
ios.fill()
}
);
}
void top()
{
if ( ! context.empty() )
{
const state_type & state = context.top();
ios.flags( state.flags );
ios.precision( state.precision );
ios.width( state.width );
ios.fill( state.fill );
}
}
void pop()
{
top();
context.pop();
}
public:
fmt( fmt && f ) : context { f.context }, ios ( f.ios )
{
f.context = context_type();
}
fmt & operator = ( fmt && f )
{
context = f.context;
ios = f.ios;
f.context = context_type();
return *this;
}
private:
fmt();
fmt( const fmt & );
void init()
{
ios.flags( std::ios_base::skipws | std::ios_base::dec );
ios.precision( 6 );
ios.width( 0 );
ios.fill( ios.widen( ' ' ) );
}
private:
struct state_type
{
const std::ios_base::fmtflags flags;
const std::streamsize precision;
const std::streamsize width;
const T fill;
};
typedef std::stack
<
const state_type,
std::vector< state_type >
>
context_type;
context_type context;
std::basic_ios< T > & ios;
};
template< typename T >
inline fmt< T > make_fmt( std::basic_ios< T > & ios )
{
return fmt< T >( ios );
}
}
#endif /* TOOLBOX_H */
Just include this header file and guard the I/O stream code at the most convenient points as needed by the particular program logic. For instance, "correcting" the previous code could be done as:
...
#include "toolbox.h"
void inner()
{
// Reset std::cout formatting to defaults
// and save this initial state
auto fmt = TB::make_fmt( std::cout );
fmt.push();
std::cout
<< "Number 1: "
...
<< std::endl;
fmt.top(); // Restore any changed formatting option
std::cout.fill( '^' );
std::cout
<< "Number 2: "
...
<< std::endl;
fmt.top();
std::cout
<< "Number 3: "
...
<< std::endl;
std::cout
<< std::endl;
fmt.top();
std::cout
<< "Floating 1: "
...
<< std::endl;
fmt.top();
std::cout.precision( 4 );
std::cout.width( 6 );
std::cout
<< "Floating 2: "
...
<< std::endl;
fmt.top();
std::cout
<< "Floating 3: "
...
<< std::endl;
fmt.pop();
}
void outer()
{
// Reset std::cout formatting to defaults
// and save this initial state
auto fmt = TB::make_fmt( std::cout );
fmt.push();
std::cout.fill( '*' );
std::cout
<< std::setw( 20 )
<< "Hello, there!"
<< std::endl
<< std::endl;
fmt.push(); // Not required but, one never knows...
inner();
fmt.pop(); // Restore fmt state as before inner().
std::cout
<< std::endl
<< std::setw( 15 )
<< "going home..."
<< std::endl;
}
...
The output will now be much closer to what would be expected:
(the additions are portable, efficient and clean)
Formatting flags ...
*******Hello, there!
Number 1: .........e
Number 2: 12^^
Number 3: 20
Floating 1: 8.33
Floating 2: 1.83
Floating 3: 3.33333 // Not perfect, but better!
**Going home...
A slight variation of the program may be more suitable for use on a more daily basis... (I may also try some renaming for attempting better clarity):
1 #ifndef TOOLBOX_H
2 #define TOOLBOX_H
3
4 #include <iostream>
5 #include <vector>
6 #include <stack>
7
8 namespace TB
9 {
10 namespace formatting
11 {
12
13 template< typename T >
14 struct context
15 {
16 explicit context( std::basic_ios< T > & ios ) : ios ( ios )
17 {
18 push();
19 }
20
21 ~context()
22 {
23 pop();
24 }
25
26 void push()
27 {
28 history.push
29 (
30 {
31 ios.flags(),
32 ios.precision(),
33 ios.width(),
34 ios.fill()
35 }
36 );
37 }
38
39 void top()
40 {
41 if ( ! history.empty() )
42 {
43 const status_type & status = history.top();
44
45 ios.flags( status.flags );
46 ios.precision( status.precision );
47 ios.width( status.width );
48 ios.fill( status.fill );
49 }
50 }
51
52 void pop()
53 {
54 top();
55 history.pop();
56 }
57
58 public:
59
60 context( context && c ) : history { c.history }, ios ( c.ios )
61 {
62 c.history = history_type();
63 }
64
65 context & operator = ( context && c )
66 {
67 history = c.history;
68 ios = c.ios;
69
70 c.history = history_type();
71
72 return *this;
73 }
74
75 private:
76
77 context();
78 context( const context & );
79
80 void reset()
81 {
82 using fmt = std::ios_base;
83
84 ios.flags( fmt::skipws | fmt::dec );
85 ios.precision( 6 );
86 ios.width( 0 );
87 ios.fill( ios.widen( ' ' ) );
88 }
89
90 private:
91
92 struct status_type
93 {
94 const std::ios_base::fmtflags flags;
95 const std::streamsize precision;
96 const std::streamsize width;
97 const T fill;
98 };
99
100 typedef std::stack
101 <
102 const status_type,
103 std::vector< status_type >
104 >
105 history_type;
106
107 history_type history;
108
109 std::basic_ios< T > & ios;
110 };
111
112 template< typename T >
113 inline context< T > make_context( std::basic_ios< T > & ios )
114 {
115 return context< T >( ios );
116 }
117
118 }
119 }
120
121 #endif /* TOOLBOX_H */
122