• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..22-Jan-2019-

.gitignoreH A D22-Jan-201960 43

.travis.ymlH A D22-Jan-20193.9 KiB197177

MakefileH A D22-Jan-20191.9 KiB5741

README.mdH A D22-Jan-201919 KiB453352

appveyor.ymlH A D22-Jan-20191.2 KiB5342

bloat_test.shH A D22-Jan-20192.9 KiB10671

tinyformat.hH A D22-Jan-201942.7 KiB1,060664

tinyformat_speed_test.cppH A D22-Jan-20191.9 KiB6552

tinyformat_test.cppH A D22-Jan-201911.1 KiB284212

README.md

1# tinyformat.h
2
3## A minimal type safe printf() replacement
4
5**tinyformat.h** is a type safe printf replacement library in a single C++
6header file.  If you've ever wanted `printf("%s", s)` to just work regardless
7of the type of `s`, tinyformat might be for you.  Design goals include:
8
9* Type safety and extensibility for user defined types.
10* C99 `printf()` compatibility, to the extent possible using `std::ostream`
11* Simplicity and minimalism.  A single header file to include and distribute
12  with your projects.
13* Augment rather than replace the standard stream formatting mechanism
14* C++98 support, with optional C++11 niceties
15
16Build status, master branch:
17[![Linux/OSX build](https://travis-ci.org/c42f/tinyformat.svg?branch=master)](https://travis-ci.org/c42f/tinyformat)
18[![Windows build](https://ci.appveyor.com/api/projects/status/rwxqhhy6v5m0p1aq/branch/master?svg=true)](https://ci.appveyor.com/project/c42f/tinyformat/branch/master)
19
20## Quickstart
21
22To print a date to `std::cout`:
23
24```C++
25std::string weekday = "Wednesday";
26const char* month = "July";
27size_t day = 27;
28long hour = 14;
29int min = 44;
30
31tfm::printf("%s, %s %d, %.2d:%.2d\n", weekday, month, day, hour, min);
32```
33
34The strange types here emphasize the type safety of the interface, for example
35it is possible to print a `std::string` using the `"%s"` conversion, and a
36`size_t` using the `"%d"` conversion.  A similar result could be achieved
37using either of the `tfm::format()` functions.  One prints on a user provided
38stream:
39
40```C++
41tfm::format(std::cerr, "%s, %s %d, %.2d:%.2d\n",
42            weekday, month, day, hour, min);
43```
44
45The other returns a `std::string`:
46
47```C++
48std::string date = tfm::format("%s, %s %d, %.2d:%.2d\n",
49                               weekday, month, day, hour, min);
50std::cout << date;
51```
52
53
54It is safe to use tinyformat inside a template function.  For any type which
55has the usual stream insertion `operator<<` defined, the following will work
56as desired:
57
58```C++
59template<typename T>
60void myPrint(const T& value)
61{
62    tfm::printf("My value is '%s'\n", value);
63}
64```
65
66(The above is a compile error for types `T` without a stream insertion
67operator.)
68
69
70## Function reference
71
72All user facing functions are defined in the namespace `tinyformat`.  A
73namespace alias `tfm` is provided to encourage brevity, but can easily be
74disabled if desired.
75
76Three main interface functions are available: an iostreams-based `format()`,
77a string-based `format()` and a `printf()` replacement.  These functions
78can be thought of as C++ replacements for C's `fprintf()`, `sprintf()` and
79`printf()` functions respectively.  All the interface functions can take an
80unlimited number of input arguments if compiled with C++11 variadic templates
81support.  In C++98 mode, the number of arguments must be limited to some fixed
82upper bound which is currently 16 as of version 1.3. Supporting more arguments
83is quite easy using the in-source code generator based on
84[cog.py](http://nedbatchelder.com/code/cog) - see the source for details.
85
86The `format()` function which takes a stream as the first argument is the
87main part of the tinyformat interface.  `stream` is the output stream,
88`formatString` is a format string in C99 `printf()` format, and the values
89to be formatted have arbitrary types:
90
91```C++
92template<typename... Args>
93void format(std::ostream& stream, const char* formatString,
94            const Args&... args);
95```
96
97The second version of `format()` is a convenience function which returns a
98`std::string` rather than printing onto a stream.  This function simply
99calls the main version of `format()` using a `std::ostringstream`, and
100returns the resulting string:
101
102```C++
103template<typename... Args>
104std::string format(const char* formatString, const Args&... args);
105```
106
107Finally, `printf()` and `printfln()` are convenience functions which call
108`format()` with `std::cout` as the first argument; both have the same
109signature:
110
111```C++
112template<typename... Args>
113void printf(const char* formatString, const Args&... args);
114```
115
116`printfln()` is the same as `printf()` but appends an additional newline
117for convenience - a concession to the author's tendency to forget the newline
118when using the library for simple logging.
119
120## Format strings and type safety
121
122Tinyformat parses C99 format strings to guide the formatting process --- please
123refer to any standard C99 printf documentation for format string syntax.  In
124contrast to printf, tinyformat does not use the format string to decide on
125the type to be formatted so this does not compromise the type safety: *you may
126use any format specifier with any C++ type*.  The author suggests standardising
127on the `%s` conversion unless formatting numeric types.
128
129Let's look at what happens when you execute the function call:
130
131```C++
132tfm::format(outStream, "%+6.4f", yourType);
133```
134
135First, the library parses the format string, and uses it to modify the state of
136`outStream`:
137
1381. The `outStream` formatting flags are cleared and the width, precision and
139   fill reset to the default.
1402. The flag `'+'` means to prefix positive numbers with a `'+'`; tinyformat
141   executes `outStream.setf(std::ios::showpos)`
1423. The number 6 gives the field width; execute `outStream.width(6)`.
1434. The number 4 gives the precision; execute `outStream.precision(4)`.
1445. The conversion specification character `'f'` means that floats should be
145   formatted with a fixed number of digits; this corresponds to executing
146   `outStream.setf(std::ios::fixed, std::ios::floatfield);`
147
148After all these steps, tinyformat executes:
149
150```C++
151outStream << yourType;
152```
153
154and finally restores the stream flags, precision and fill.
155
156What happens if `yourType` isn't actually a floating point type?  In this
157case the flags set above are probably irrelevant and will be ignored by the
158underlying `std::ostream` implementation.  The field width of six may cause
159some padding in the output of `yourType`, but that's about it.
160
161
162### Special cases for "%p", "%c" and "%s"
163
164Tinyformat normally uses `operator<<` to convert types to strings.  However,
165the "%p" and "%c" conversions require special rules for robustness.  Consider:
166
167```C++
168uint8_t* pixels = get_pixels(/* ... */);
169tfm::printf("%p", pixels);
170```
171
172Clearly the intention here is to print a representation of the *pointer* to
173`pixels`, but since `uint8_t` is a character type the compiler would
174attempt to print it as a C string if we blindly fed it into `operator<<`.  To
175counter this kind of madness, tinyformat tries to static_cast any type fed to
176the "%p" conversion into a `const void*` before printing.  If this can't be
177done at compile time the library falls back to using `operator<<` as usual.
178
179The "%c" conversion has a similar problem: it signifies that the given integral
180type should be converted into a `char` before printing.  The solution is
181identical: attempt to convert the provided type into a char using
182`static_cast` if possible, and if not fall back to using `operator<<`.
183
184The "%s" conversion sets the boolalpha flag on the formatting stream.  This
185means that a `bool` variable printed with "%s" will come out as `true` or
186`false` rather than the `1` or `0` that you would otherwise get.
187
188
189### Incompatibilities with C99 printf
190
191Not all features of printf can be simulated simply using standard iostreams.
192Here's a list of known incompatibilities:
193
194* The C99 `"%a"` and `"%A"` hexadecimal floating point conversions are not
195  supported since the iostreams don't have the necessary flags.  Using either
196  of these flags will result in a call to `TINYFORMAT_ERROR`.
197* The precision for integer conversions cannot be supported by the iostreams
198  state independently of the field width.  (Note: **this is only a
199  problem for certain obscure integer conversions**; float conversions like
200  `%6.4f` work correctly.)  In tinyformat the field width takes precedence,
201  so the 4 in `%6.4d` will be ignored.  However, if the field width is not
202  specified, the width used internally is set equal to the precision and padded
203  with zeros on the left.  That is, a conversion like `%.4d` effectively
204  becomes `%04d` internally.  This isn't correct for every case (eg, negative
205  numbers end up with one less digit than desired) but it's about the closest
206  simple solution within the iostream model.
207* The `"%n"` query specifier isn't supported to keep things simple and will
208  result in a call to `TINYFORMAT_ERROR`.
209* The `"%ls"` conversion is not supported, and attempting to format a
210  `wchar_t` array will cause a compile time error to minimise unexpected
211  surprises.  If you know the encoding of your wchar_t strings, you could write
212  your own `std::ostream` insertion operator for them, and disable the
213  compile time check by defining the macro `TINYFORMAT_ALLOW_WCHAR_STRINGS`.
214  If you want to print the *address* of a wide character with the `"%p"`
215  conversion, you should cast it to a `void*` before passing it to one of the
216  formatting functions.
217
218
219## Error handling
220
221By default, tinyformat calls `assert()` if it encounters an error in the
222format string or number of arguments.  This behaviour can be changed (for
223example, to throw an exception) by defining the `TINYFORMAT_ERROR` macro
224before including tinyformat.h, or editing the config section of the header.
225
226
227## Formatting user defined types
228
229User defined types with a stream insertion operator will be formatted using
230`operator<<(std::ostream&, T)` by default.  The `"%s"` format specifier is
231suggested for user defined types, unless the type is inherently numeric.
232
233For further customization, the user can override the `formatValue()`
234function to specify formatting independently of the stream insertion operator.
235If you override this function, the library will have already parsed the format
236specification and set the stream flags accordingly - see the source for details.
237
238
239## Wrapping tfm::format() inside a user defined format function
240
241Suppose you wanted to define your own function which wraps `tfm::format`.
242For example, consider an error function taking an error code, which in C++11
243might be written simply as:
244
245```C++
246template<typename... Args>
247void error(int code, const char* fmt, const Args&... args)
248{
249    std::cerr << "error (code " << code << ")";
250    tfm::format(std::cerr, fmt, args...);
251}
252```
253
254Simulating this functionality in C++98 is pretty painful since it requires
255writing out a version of `error()` for each desired number of arguments.  To
256make this bearable tinyformat comes with a set of macros which are used
257internally to generate the API, but which may also be used in user code.
258
259The three macros `TINYFORMAT_ARGTYPES(n)`, `TINYFORMAT_VARARGS(n)` and
260`TINYFORMAT_PASSARGS(n)` will generate a list of `n` argument types,
261type/name pairs and argument names respectively when called with an integer
262`n` between 1 and 16.  We can use these to define a macro which generates the
263desired user defined function with `n` arguments.  This should be followed by
264a call to `TINYFORMAT_FOREACH_ARGNUM` to generate the set of functions for
265all supported `n`:
266
267```C++
268#define MAKE_ERROR_FUNC(n)                                    \
269template<TINYFORMAT_ARGTYPES(n)>                              \
270void error(int code, const char* fmt, TINYFORMAT_VARARGS(n))  \
271{                                                             \
272    std::cerr << "error (code " << code << ")";               \
273    tfm::format(std::cerr, fmt, TINYFORMAT_PASSARGS(n));      \
274}
275TINYFORMAT_FOREACH_ARGNUM(MAKE_ERROR_FUNC)
276```
277
278Sometimes it's useful to be able to pass a list of format arguments through to
279a non-template function.  The `FormatList` class is provided as a way to do
280this by storing the argument list in a type-opaque way.  For example:
281
282```C++
283template<typename... Args>
284void error(int code, const char* fmt, const Args&... args)
285{
286    tfm::FormatListRef formatList = tfm::makeFormatList(args...);
287    errorImpl(code, fmt, formatList);
288}
289```
290
291What's interesting here is that `errorImpl()` is a non-template function so
292it could be separately compiled if desired.  The `FormatList` instance can be
293used via a call to the `vformat()` function (the name chosen for semantic
294similarity to `vprintf()`):
295
296```C++
297void errorImpl(int code, const char* fmt, tfm::FormatListRef formatList)
298{
299    std::cerr << "error (code " << code << ")";
300    tfm::vformat(std::cout, fmt, formatList);
301}
302```
303
304The construction of a `FormatList` instance is very lightweight - it defers
305all formatting and simply stores a couple of function pointers and a value
306pointer per argument.  Since most of the actual work is done inside
307`vformat()`, any logic which causes an early exit of `errorImpl()` -
308filtering of verbose log messages based on error code for example - could be a
309useful optimization for programs using tinyformat.  (A faster option would be
310to write any early bailout code inside `error()`, though this must be done in
311the header.)
312
313
314## Benchmarks
315
316### Compile time and code bloat
317
318The script `bloat_test.sh` included in the repository tests whether
319tinyformat succeeds in avoiding compile time and code bloat for nontrivial
320projects.  The idea is to include `tinyformat.h` into 100 translation units
321and use `printf()` five times in each to simulate a medium sized project.
322The resulting executable size and compile time (g++-4.8.2, linux ubuntu 14.04)
323is shown in the following tables, which can be regenerated using `make
324bloat_test`:
325
326**Non-optimized build**
327
328| test name              | compiler wall time | executable size (stripped) |
329| ---------------------- | ------------------ | -------------------------- |
330| libc printf            | 1.8s               | 48K  (36K)                 |
331| std::ostream           | 10.7s              | 96K  (76K)                 |
332| tinyformat, no inlines | 18.9s              | 140K (104K)                |
333| tinyformat             | 21.1s              | 220K (180K)                |
334| tinyformat, c++0x mode | 20.7s              | 220K (176K)                |
335| boost::format          | 70.1s              | 844K (736K)                |
336
337**Optimized build (-O3 -DNDEBUG)**
338
339| test name              | compiler wall time | executable size (stripped) |
340| ---------------------- | ------------------ | -------------------------- |
341| libc printf            | 2.3s               | 40K  (28K)                 |
342| std::ostream           | 11.8s              | 104K (80K)                 |
343| tinyformat, no inlines | 23.0s              | 128K (104K)                |
344| tinyformat             | 32.9s              | 128K (104K)                |
345| tinyformat, c++0x mode | 34.0s              | 128K (104K)                |
346| boost::format          | 147.9s             | 644K (600K)                |
347
348For large projects it's arguably worthwhile to do separate compilation of the
349non-templated parts of tinyformat, as shown in the rows labelled *tinyformat,
350no inlines*.  These were generated by putting the implementation of `vformat`
351(`detail::formatImpl()` etc) it into a separate file, tinyformat.cpp.  Note
352that the results above can vary considerably with different compilers.  For
353example, the `-fipa-cp-clone` optimization pass in g++-4.6 resulted in
354excessively large binaries.  On the other hand, the g++-4.8 results are quite
355similar to using clang++-3.4.
356
357
358### Speed tests
359
360The following speed tests results were generated by building
361`tinyformat_speed_test.cpp` on an Intel core i7-2600K running Linux Ubuntu
36214.04 with g++-4.8.2 using `-O3 -DNDEBUG`.  In the test, the format string
363`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` is filled 2000000 times with output sent to
364`/dev/null`; for further details see the source and Makefile.
365
366| test name      | run time |
367| -------------- | -------- |
368| libc printf    | 1.20s    |
369| std::ostream   | 1.82s    |
370| tinyformat     | 2.08s    |
371| boost::format  | 9.04s    |
372
373It's likely that tinyformat has an advantage over boost.format because it tries
374reasonably hard to avoid formatting into temporary strings, preferring instead
375to send the results directly to the stream buffer.  Tinyformat cannot
376be faster than the iostreams because it uses them internally, but it comes
377acceptably close.
378
379
380## Rationale
381
382Or, why did I reinvent this particularly well studied wheel?
383
384Nearly every program needs text formatting in some form but in many cases such
385formatting is *incidental* to the main purpose of the program.  In these cases,
386you really want a library which is simple to use but as lightweight as
387possible.
388
389The ultimate in lightweight dependencies are the solutions provided by the C++
390and C libraries.  However, both the C++ iostreams and C's printf() have
391well known usability problems: iostreams are hopelessly verbose for complicated
392formatting and printf() lacks extensibility and type safety.  For example:
393
394```C++
395// Verbose; hard to read, hard to type:
396std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
397// The alternative using a format string is much easier on the eyes
398tfm::printf("%.2f\n", 1.23456);
399
400// Type mismatch between "%s" and int: will cause a segfault at runtime!
401printf("%s", 1);
402// The following is perfectly fine, and will result in "1" being printed.
403tfm::printf("%s", 1);
404```
405
406On the other hand, there are plenty of excellent and complete libraries which
407solve the formatting problem in great generality (boost.format and fastformat
408come to mind, but there are many others).  Unfortunately these kind of
409libraries tend to be rather heavy dependencies, far too heavy for projects
410which need to do only a little formatting.  Problems include
411
4121. Having many large source files.  This makes a heavy dependency unsuitable to
413   bundle within other projects for convenience.
4142. Slow build times for every file using any sort of formatting (this is very
415   noticeable with g++ and boost/format.hpp. I'm not sure about the various
416   other alternatives.)
4173. Code bloat due to instantiating many templates
418
419Tinyformat tries to solve these problems while providing formatting which is
420sufficiently general and fast for incidental day to day uses.
421
422
423## License
424
425For minimum license-related fuss, tinyformat.h is distributed under the boost
426software license, version 1.0.  (Summary: you must keep the license text on
427all source copies, but don't have to mention tinyformat when distributing
428binaries.)
429
430
431## Author and acknowledgements
432
433Tinyformat was written by Chris Foster, with contributions from various people
434as recorded in the git repository.
435The implementation owes a lot to `boost::format` for showing that it's fairly
436easy to use stream based formatting to simulate most of the `printf()`
437syntax.  Douglas Gregor's introduction to variadic templates --- see
438http://www.generic-programming.org/~dgregor/cpp/variadic-templates.html --- was
439also helpful, especially since it solves exactly the `printf()` problem for
440the case of trivial format strings.
441
442## Bugs
443
444Here's a list of known bugs which are probably cumbersome to fix:
445
446* Field padding won't work correctly with complicated user defined types.  For
447  general types, the only way to do this correctly seems to be format to a
448  temporary string stream, check the length, and finally send to the output
449  stream with padding if necessary.  Doing this for all types would be
450  quite inelegant because it implies extra allocations to make the temporary
451  stream.  A workaround is to add logic to `operator<<()` for composite user
452  defined types so they are aware of the stream field width.
453