1 // Boost.TypeErasure library
2 //
3 // Copyright 2012 Steven Watanabe
4 //
5 // Distributed under the Boost Software License Version 1.0. (See
6 // accompanying file LICENSE_1_0.txt or copy at
7 // http://www.boost.org/LICENSE_1_0.txt)
8 //
9 // $Id$
10 
11 //[printf
12 /*`
13     (For the source of this example see
14     [@boost:/libs/type_erasure/example/printf.cpp printf.cpp])
15 
16     This example uses the library to implement a type safe printf.
17 
18     [note This example uses C++11 features.  You'll need a
19     recent compiler for it to work.]
20  */
21 
22 #include <boost/type_erasure/builtin.hpp>
23 #include <boost/type_erasure/operators.hpp>
24 #include <boost/type_erasure/any_cast.hpp>
25 #include <boost/type_erasure/any.hpp>
26 #include <boost/mpl/vector.hpp>
27 #include <boost/io/ios_state.hpp>
28 #include <iostream>
29 #include <sstream>
30 #include <iomanip>
31 #include <vector>
32 #include <string>
33 
34 namespace mpl = boost::mpl;
35 using namespace boost::type_erasure;
36 using namespace boost::io;
37 
38 // We capture the arguments by reference and require nothing
39 // except that each one must provide a stream insertion operator.
40 typedef any<
41     mpl::vector<
42         typeid_<>,
43         ostreamable<>
44     >,
45     const _self&
46 > any_printable;
47 typedef std::vector<any_printable> print_storage;
48 
49 // Forward declaration of the implementation function
50 void print_impl(std::ostream& os, const char * format, const print_storage& args);
51 
52 // print
53 //
54 // Writes values to a stream like the classic C printf function.  The
55 // arguments are formatted based on specifiers in the format string,
56 // which match the pattern:
57 //
58 // '%' [ argument-number '$' ] flags * [ width ] [ '.' precision ] [ type-code ] format-specifier
59 //
60 // Other characters in the format string are written to the stream unchanged.
61 // In addition the sequence, "%%" can be used to print a literal '%' character.
62 // Each component is explained in detail below
63 //
64 // argument-number:
65 //   The value must be between 1 and sizeof... T.  It indicates the
66 //   index of the argument to be formatted.  If no index is specified
67 //   the arguments will be processed sequentially.  If an index is
68 //   specified for one argument, then it must be specified for every argument.
69 //
70 // flags:
71 //   Consists of zero or more of the following:
72 //   '-': Left justify the argument
73 //   '+': Print a plus sign for positive integers
74 //   '0': Use leading 0's to pad instead of filling with spaces.
75 //   ' ': If the value doesn't begin with a sign, prepend a space
76 //   '#': Print 0x or 0 for hexadecimal and octal numbers.
77 //
78 // width:
79 //   Indicates the minimum width to print.  This can be either
80 //   an integer or a '*'.  an asterisk means to read the next
81 //   argument (which must have type int) as the width.
82 //
83 // precision:
84 //   For numeric arguments, indicates the number of digits to print.  For
85 //   strings (%s) the precision indicates the maximum number of characters
86 //   to print.  Longer strings will be truncated.  As with width
87 //   this can be either an integer or a '*'.  an asterisk means
88 //   to read the next argument (which must have type int) as
89 //   the width.  If both the width and the precision are specified
90 //   as '*', the width is read first.
91 //
92 // type-code:
93 //   This is ignored, but provided for compatibility with C printf.
94 //
95 // format-specifier:
96 //   Must be one of the following characters:
97 //   d, i, u: The argument is formatted as a decimal integer
98 //   o:       The argument is formatted as an octal integer
99 //   x, X:    The argument is formatted as a hexadecimal integer
100 //   p:       The argument is formatted as a pointer
101 //   f:       The argument is formatted as a fixed point decimal
102 //   e, E:    The argument is formatted in exponential notation
103 //   g, G:    The argument is formatted as either fixed point or using
104 //            scientific notation depending on its magnitude
105 //   c:       The argument is formatted as a character
106 //   s:       The argument is formatted as a string
107 //
108 template<class... T>
print(std::ostream & os,const char * format,const T &...t)109 void print(std::ostream& os, const char * format, const T&... t)
110 {
111     // capture the arguments
112     print_storage args = { any_printable(t)... };
113     // and forward to the real implementation
114     print_impl(os, format, args);
115 }
116 
117 // This overload of print with no explicit stream writes to std::cout.
118 template<class... T>
print(const char * format,const T &...t)119 void print(const char * format, const T&... t)
120 {
121     print(std::cout, format, t...);
122 }
123 
124 // The implementation from here on can be separately compiled.
125 
126 // utility function to parse an integer
parse_int(const char * & format)127 int parse_int(const char *& format) {
128     int result = 0;
129     while(char ch = *format) {
130         switch(ch) {
131         case '0': case '1': case '2': case '3': case '4':
132         case '5': case '6': case '7': case '8': case '9':
133             result = result * 10 + (ch - '0');
134             break;
135         default: return result;
136         }
137         ++format;
138     }
139     return result;
140 }
141 
142 // printf implementation
print_impl(std::ostream & os,const char * format,const print_storage & args)143 void print_impl(std::ostream& os, const char * format, const print_storage& args) {
144     int idx = 0;
145     ios_flags_saver savef_outer(os, std::ios_base::dec);
146     bool has_positional = false;
147     bool has_indexed = false;
148     while(char ch = *format++) {
149         if (ch == '%') {
150             if (*format == '%') { os << '%'; continue; }
151 
152             ios_flags_saver savef(os);
153             ios_precision_saver savep(os);
154             ios_fill_saver savefill(os);
155 
156             int precision = 0;
157             bool pad_space = false;
158             bool pad_zero = false;
159 
160             // parse argument index
161             if (*format != '0') {
162                 int i = parse_int(format);
163                 if (i != 0) {
164                     if(*format == '$') {
165                         idx = i - 1;
166                         has_indexed = true;
167                         ++format;
168                     } else {
169                         os << std::setw(i);
170                         has_positional = true;
171                         goto parse_precision;
172                     }
173                 } else {
174                     has_positional = true;
175                 }
176             } else {
177                 has_positional = true;
178             }
179 
180             // Parse format modifiers
181             while((ch = *format)) {
182                 switch(ch) {
183                 case '-': os << std::left; break;
184                 case '+': os << std::showpos; break;
185                 case '0': pad_zero = true; break;
186                 case ' ': pad_space = true; break;
187                 case '#': os << std::showpoint << std::showbase; break;
188                 default: goto parse_width;
189                 }
190                 ++format;
191             }
192 
193         parse_width:
194             int width;
195             if (*format == '*') {
196                 ++format;
197                 width = any_cast<int>(args.at(idx++));
198             } else {
199                 width = parse_int(format);
200             }
201             os << std::setw(width);
202 
203         parse_precision:
204             if (*format == '.') {
205                 ++format;
206                 if (*format == '*') {
207                     ++format;
208                     precision = any_cast<int>(args.at(idx++));
209                 } else {
210                     precision = parse_int(format);
211                 }
212                 os << std::setprecision(precision);
213             }
214 
215             // parse (and ignore) the type modifier
216             switch(*format) {
217             case 'h': ++format; if(*format == 'h') ++format; break;
218             case 'l': ++format; if(*format == 'l') ++format; break;
219             case 'j':
220             case 'L':
221             case 'q':
222             case 't':
223             case 'z':
224                 ++format; break;
225             }
226 
227             std::size_t truncate = 0;
228 
229             // parse the format code
230             switch(*format++) {
231             case 'd': case 'i': case 'u': os << std::dec; break;
232             case 'o': os << std::oct; break;
233             case 'p': case 'x': os << std::hex; break;
234             case 'X': os << std::uppercase << std::hex; break;
235             case 'f': os << std::fixed; break;
236             case 'e': os << std::scientific; break;
237             case 'E': os << std::uppercase << std::scientific; break;
238             case 'g': break;
239             case 'G': os << std::uppercase; break;
240             case 'c': case 'C': break;
241             case 's': case 'S': truncate = precision; os << std::setprecision(6); break;
242             default: assert(!"Bad format string");
243             }
244 
245             if (pad_zero && !(os.flags() & std::ios_base::left)) {
246                 os << std::setfill('0') << std::internal;
247                 pad_space = false;
248             }
249 
250             if (truncate != 0 || pad_space) {
251                 // These can't be handled by std::setw.  Write to a stringstream and
252                 // pad/truncate manually.
253                 std::ostringstream oss;
254                 oss.copyfmt(os);
255                 oss << args.at(idx++);
256                 std::string data = oss.str();
257 
258                 if (pad_space) {
259                     if (data.empty() || (data[0] != '+' && data[0] != '-' && data[0] != ' ')) {
260                         os << ' ';
261                     }
262                 }
263                 if (truncate != 0 && data.size() > truncate) {
264                     data.resize(truncate);
265                 }
266                 os << data;
267             } else {
268                 os << args.at(idx++);
269             }
270 
271             // we can't have both positional and indexed arguments in
272             // the format string.
273             assert(has_positional ^ has_indexed);
274 
275         } else {
276             std::cout << ch;
277         }
278     }
279 }
280 
main()281 int main() {
282     print("int: %d\n", 10);
283     print("int: %0#8X\n", 0xA56E);
284     print("double: %g\n", 3.14159265358979323846);
285     print("double: %f\n", 3.14159265358979323846);
286     print("double: %+20.9e\n", 3.14159265358979323846);
287     print("double: %0+20.9g\n", 3.14159265358979323846);
288     print("double: %*.*g\n", 20, 5, 3.14159265358979323846);
289     print("string: %.10s\n", "Hello World!");
290     print("double: %2$*.*g int: %1$d\n", 10, 20, 5, 3.14159265358979323846);
291 }
292 
293 //]
294