├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── Makefile ├── README.md ├── appveyor.yml ├── bloat_test.sh ├── tinyformat.h ├── tinyformat_speed_test.cpp └── tinyformat_test.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | _empty.cpp 3 | _bloat_test_tmp* 4 | tinyformat_test_cxx* 5 | tinyformat_speed_test 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | sudo: false 4 | 5 | # C'mon Travis, you're a great service, but why do I need a script to generate my build matrix? 6 | 7 | # cat $0 | sed -e 's/^\(.\)/# \1/' -e 's/^$/#/' 8 | # 9 | # cat < /dev/null && \ 15 | echo "No errors" || echo "Tests failed" 16 | 17 | doc: tinyformat.html 18 | 19 | speed_test: tinyformat_speed_test 20 | @echo running speed tests... 21 | @echo printf timings: 22 | @time -p ./tinyformat_speed_test printf > /dev/null 23 | @echo iostreams timings: 24 | @time -p ./tinyformat_speed_test iostreams > /dev/null 25 | @echo tinyformat timings: 26 | @time -p ./tinyformat_speed_test tinyformat > /dev/null 27 | @echo boost timings: 28 | @time -p ./tinyformat_speed_test boost > /dev/null 29 | 30 | # To test for multiple definitions 31 | _empty.cpp: 32 | echo '#include "tinyformat.h"' > _empty.cpp 33 | 34 | tinyformat_test_cxx98: tinyformat.h tinyformat_test.cpp _empty.cpp Makefile 35 | $(CXX) $(CXXFLAGS) -std=c++98 -DTINYFORMAT_NO_VARIADIC_TEMPLATES _empty.cpp tinyformat_test.cpp -o tinyformat_test_cxx98 36 | 37 | tinyformat_test_cxx11: tinyformat.h tinyformat_test.cpp _empty.cpp Makefile 38 | $(CXX) $(CXXFLAGS) $(CXX11FLAGS) -DTINYFORMAT_USE_VARIADIC_TEMPLATES _empty.cpp tinyformat_test.cpp -o tinyformat_test_cxx11 39 | 40 | tinyformat.html: README.rst 41 | @echo building docs... 42 | rst2html.py README.rst > tinyformat.html 43 | 44 | tinyformat_speed_test: tinyformat.h tinyformat_speed_test.cpp Makefile 45 | $(CXX) $(CXXFLAGS) -O3 -DNDEBUG tinyformat_speed_test.cpp -o tinyformat_speed_test 46 | 47 | bloat_test: 48 | @for opt in '' '-O3 -DNDEBUG' ; do \ 49 | for use in '' '-DUSE_IOSTREAMS' '-DUSE_TINYFORMAT' '-DUSE_TINYFORMAT $(CXX11FLAGS)' '-DUSE_BOOST' ; do \ 50 | echo ; \ 51 | echo ./bloat_test.sh $(CXX) $$opt $$use ; \ 52 | ./bloat_test.sh $(CXX) $$opt $$use ; \ 53 | done ; \ 54 | done 55 | 56 | 57 | clean: 58 | rm -f tinyformat_test_cxx98 tinyformat_test_cxx11 tinyformat_speed_test 59 | rm -f tinyformat.html 60 | rm -f _bloat_test_tmp_* 61 | -------------------------------------------------------------------------------- /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++ 6 | header file. If you've ever wanted `printf("%s", s)` to just work regardless 7 | of 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 | * POSIX extension for positional arguments 12 | * Simplicity and minimalism. A single header file to include and distribute 13 | with your projects. 14 | * Augment rather than replace the standard stream formatting mechanism 15 | * C++98 support, with optional C++11 niceties 16 | 17 | Build status, master branch: 18 | [![Linux/OSX build](https://travis-ci.org/c42f/tinyformat.svg?branch=master)](https://travis-ci.org/c42f/tinyformat) 19 | [![Windows build](https://ci.appveyor.com/api/projects/status/rwxqhhy6v5m0p1aq/branch/master?svg=true)](https://ci.appveyor.com/project/c42f/tinyformat/branch/master) 20 | 21 | ## Quickstart 22 | 23 | To print a date to `std::cout`: 24 | 25 | ```C++ 26 | std::string weekday = "Wednesday"; 27 | const char* month = "July"; 28 | size_t day = 27; 29 | long hour = 14; 30 | int min = 44; 31 | 32 | tfm::printf("%s, %s %d, %.2d:%.2d\n", weekday, month, day, hour, min); 33 | ``` 34 | 35 | POSIX extension for positional arguments is available. 36 | The ability to rearrange formatting arguments is an important feature 37 | for localization because the word order may vary in different languages. 38 | 39 | Previous example for German usage. Arguments are reordered: 40 | 41 | ```C++ 42 | tfm::printf("%1$s, %3$d. %2$s, %4$d:%5$.2d\n", weekday, month, day, hour, min); 43 | ``` 44 | 45 | The strange types here emphasize the type safety of the interface, for example 46 | it is possible to print a `std::string` using the `"%s"` conversion, and a 47 | `size_t` using the `"%d"` conversion. A similar result could be achieved 48 | using either of the `tfm::format()` functions. One prints on a user provided 49 | stream: 50 | 51 | ```C++ 52 | tfm::format(std::cerr, "%s, %s %d, %.2d:%.2d\n", 53 | weekday, month, day, hour, min); 54 | ``` 55 | 56 | The other returns a `std::string`: 57 | 58 | ```C++ 59 | std::string date = tfm::format("%s, %s %d, %.2d:%.2d\n", 60 | weekday, month, day, hour, min); 61 | std::cout << date; 62 | ``` 63 | 64 | 65 | It is safe to use tinyformat inside a template function. For any type which 66 | has the usual stream insertion `operator<<` defined, the following will work 67 | as desired: 68 | 69 | ```C++ 70 | template 71 | void myPrint(const T& value) 72 | { 73 | tfm::printf("My value is '%s'\n", value); 74 | } 75 | ``` 76 | 77 | (The above is a compile error for types `T` without a stream insertion 78 | operator.) 79 | 80 | 81 | ## Function reference 82 | 83 | All user facing functions are defined in the namespace `tinyformat`. A 84 | namespace alias `tfm` is provided to encourage brevity, but can easily be 85 | disabled if desired. 86 | 87 | Three main interface functions are available: an iostreams-based `format()`, 88 | a string-based `format()` and a `printf()` replacement. These functions 89 | can be thought of as C++ replacements for C's `fprintf()`, `sprintf()` and 90 | `printf()` functions respectively. All the interface functions can take an 91 | unlimited number of input arguments if compiled with C++11 variadic templates 92 | support. In C++98 mode, the number of arguments must be limited to some fixed 93 | upper bound which is currently 16 as of version 1.3. Supporting more arguments 94 | is quite easy using the in-source code generator based on 95 | [cog.py](http://nedbatchelder.com/code/cog) - see the source for details. 96 | 97 | The `format()` function which takes a stream as the first argument is the 98 | main part of the tinyformat interface. `stream` is the output stream, 99 | `formatString` is a format string in C99 `printf()` format, and the values 100 | to be formatted have arbitrary types: 101 | 102 | ```C++ 103 | template 104 | void format(std::ostream& stream, const char* formatString, 105 | const Args&... args); 106 | ``` 107 | 108 | The second version of `format()` is a convenience function which returns a 109 | `std::string` rather than printing onto a stream. This function simply 110 | calls the main version of `format()` using a `std::ostringstream`, and 111 | returns the resulting string: 112 | 113 | ```C++ 114 | template 115 | std::string format(const char* formatString, const Args&... args); 116 | ``` 117 | 118 | Finally, `printf()` and `printfln()` are convenience functions which call 119 | `format()` with `std::cout` as the first argument; both have the same 120 | signature: 121 | 122 | ```C++ 123 | template 124 | void printf(const char* formatString, const Args&... args); 125 | ``` 126 | 127 | `printfln()` is the same as `printf()` but appends an additional newline 128 | for convenience - a concession to the author's tendency to forget the newline 129 | when using the library for simple logging. 130 | 131 | ## Format strings and type safety 132 | 133 | Tinyformat parses C99 format strings to guide the formatting process --- please 134 | refer to any standard C99 printf documentation for format string syntax. In 135 | contrast to printf, tinyformat does not use the format string to decide on 136 | the type to be formatted so this does not compromise the type safety: *you may 137 | use any format specifier with any C++ type*. The author suggests standardising 138 | on the `%s` conversion unless formatting numeric types. 139 | 140 | Let's look at what happens when you execute the function call: 141 | 142 | ```C++ 143 | tfm::format(outStream, "%+6.4f", yourType); 144 | ``` 145 | 146 | First, the library parses the format string, and uses it to modify the state of 147 | `outStream`: 148 | 149 | 1. The `outStream` formatting flags are cleared and the width, precision and 150 | fill reset to the default. 151 | 2. The flag `'+'` means to prefix positive numbers with a `'+'`; tinyformat 152 | executes `outStream.setf(std::ios::showpos)` 153 | 3. The number 6 gives the field width; execute `outStream.width(6)`. 154 | 4. The number 4 gives the precision; execute `outStream.precision(4)`. 155 | 5. The conversion specification character `'f'` means that floats should be 156 | formatted with a fixed number of digits; this corresponds to executing 157 | `outStream.setf(std::ios::fixed, std::ios::floatfield);` 158 | 159 | After all these steps, tinyformat executes: 160 | 161 | ```C++ 162 | outStream << yourType; 163 | ``` 164 | 165 | and finally restores the stream flags, precision and fill. 166 | 167 | What happens if `yourType` isn't actually a floating point type? In this 168 | case the flags set above are probably irrelevant and will be ignored by the 169 | underlying `std::ostream` implementation. The field width of six may cause 170 | some padding in the output of `yourType`, but that's about it. 171 | 172 | 173 | ### Special cases for "%p", "%c" and "%s" 174 | 175 | Tinyformat normally uses `operator<<` to convert types to strings. However, 176 | the "%p" and "%c" conversions require special rules for robustness. Consider: 177 | 178 | ```C++ 179 | uint8_t* pixels = get_pixels(/* ... */); 180 | tfm::printf("%p", pixels); 181 | ``` 182 | 183 | Clearly the intention here is to print a representation of the *pointer* to 184 | `pixels`, but since `uint8_t` is a character type the compiler would 185 | attempt to print it as a C string if we blindly fed it into `operator<<`. To 186 | counter this kind of madness, tinyformat tries to static_cast any type fed to 187 | the "%p" conversion into a `const void*` before printing. If this can't be 188 | done at compile time the library falls back to using `operator<<` as usual. 189 | 190 | The "%c" conversion has a similar problem: it signifies that the given integral 191 | type should be converted into a `char` before printing. The solution is 192 | identical: attempt to convert the provided type into a char using 193 | `static_cast` if possible, and if not fall back to using `operator<<`. 194 | 195 | The "%s" conversion sets the boolalpha flag on the formatting stream. This 196 | means that a `bool` variable printed with "%s" will come out as `true` or 197 | `false` rather than the `1` or `0` that you would otherwise get. 198 | 199 | 200 | ### Incompatibilities with C99 printf 201 | 202 | Not all features of printf can be simulated simply using standard iostreams. 203 | Here's a list of known incompatibilities: 204 | 205 | * The `"%a"` and `"%A"` hexadecimal floating point conversions ignore precision 206 | as stream output of hexfloat (introduced in C++11) ignores precision, always 207 | outputting the minimum number of digits required for exact representation. 208 | MSVC incorrectly honors stream precision, so we force precision to 13 in this 209 | case to guarentee lossless roundtrip conversion. 210 | * The precision for integer conversions cannot be supported by the iostreams 211 | state independently of the field width. (Note: **this is only a 212 | problem for certain obscure integer conversions**; float conversions like 213 | `%6.4f` work correctly.) In tinyformat the field width takes precedence, 214 | so the 4 in `%6.4d` will be ignored. However, if the field width is not 215 | specified, the width used internally is set equal to the precision and padded 216 | with zeros on the left. That is, a conversion like `%.4d` effectively 217 | becomes `%04d` internally. This isn't correct for every case (eg, negative 218 | numbers end up with one less digit than desired) but it's about the closest 219 | simple solution within the iostream model. 220 | * The `"%n"` query specifier isn't supported to keep things simple and will 221 | result in a call to `TINYFORMAT_ERROR`. 222 | * The `"%ls"` conversion is not supported, and attempting to format a 223 | `wchar_t` array will cause a compile time error to minimise unexpected 224 | surprises. If you know the encoding of your wchar_t strings, you could write 225 | your own `std::ostream` insertion operator for them, and disable the 226 | compile time check by defining the macro `TINYFORMAT_ALLOW_WCHAR_STRINGS`. 227 | If you want to print the *address* of a wide character with the `"%p"` 228 | conversion, you should cast it to a `void*` before passing it to one of the 229 | formatting functions. 230 | 231 | 232 | ## Error handling 233 | 234 | By default, tinyformat calls `assert()` if it encounters an error in the 235 | format string or number of arguments. This behaviour can be changed (for 236 | example, to throw an exception) by defining the `TINYFORMAT_ERROR` macro 237 | before including tinyformat.h, or editing the config section of the header. 238 | 239 | 240 | ## Formatting user defined types 241 | 242 | User defined types with a stream insertion operator will be formatted using 243 | `operator<<(std::ostream&, T)` by default. The `"%s"` format specifier is 244 | suggested for user defined types, unless the type is inherently numeric. 245 | 246 | For further customization, the user can override the `formatValue()` 247 | function to specify formatting independently of the stream insertion operator. 248 | If you override this function, the library will have already parsed the format 249 | specification and set the stream flags accordingly - see the source for details. 250 | 251 | 252 | ## Wrapping tfm::format() inside a user defined format function 253 | 254 | Suppose you wanted to define your own function which wraps `tfm::format`. 255 | For example, consider an error function taking an error code, which in C++11 256 | might be written simply as: 257 | 258 | ```C++ 259 | template 260 | void error(int code, const char* fmt, const Args&... args) 261 | { 262 | std::cerr << "error (code " << code << ")"; 263 | tfm::format(std::cerr, fmt, args...); 264 | } 265 | ``` 266 | 267 | Simulating this functionality in C++98 is pretty painful since it requires 268 | writing out a version of `error()` for each desired number of arguments. To 269 | make this bearable tinyformat comes with a set of macros which are used 270 | internally to generate the API, but which may also be used in user code. 271 | 272 | The three macros `TINYFORMAT_ARGTYPES(n)`, `TINYFORMAT_VARARGS(n)` and 273 | `TINYFORMAT_PASSARGS(n)` will generate a list of `n` argument types, 274 | type/name pairs and argument names respectively when called with an integer 275 | `n` between 1 and 16. We can use these to define a macro which generates the 276 | desired user defined function with `n` arguments. This should be followed by 277 | a call to `TINYFORMAT_FOREACH_ARGNUM` to generate the set of functions for 278 | all supported `n`: 279 | 280 | ```C++ 281 | #define MAKE_ERROR_FUNC(n) \ 282 | template \ 283 | void error(int code, const char* fmt, TINYFORMAT_VARARGS(n)) \ 284 | { \ 285 | std::cerr << "error (code " << code << ")"; \ 286 | tfm::format(std::cerr, fmt, TINYFORMAT_PASSARGS(n)); \ 287 | } 288 | TINYFORMAT_FOREACH_ARGNUM(MAKE_ERROR_FUNC) 289 | ``` 290 | 291 | Sometimes it's useful to be able to pass a list of format arguments through to 292 | a non-template function. The `FormatList` class is provided as a way to do 293 | this by storing the argument list in a type-opaque way. For example: 294 | 295 | ```C++ 296 | template 297 | void error(int code, const char* fmt, const Args&... args) 298 | { 299 | tfm::FormatListRef formatList = tfm::makeFormatList(args...); 300 | errorImpl(code, fmt, formatList); 301 | } 302 | ``` 303 | 304 | What's interesting here is that `errorImpl()` is a non-template function so 305 | it could be separately compiled if desired. The `FormatList` instance can be 306 | used via a call to the `vformat()` function (the name chosen for semantic 307 | similarity to `vprintf()`): 308 | 309 | ```C++ 310 | void errorImpl(int code, const char* fmt, tfm::FormatListRef formatList) 311 | { 312 | std::cerr << "error (code " << code << ")"; 313 | tfm::vformat(std::cout, fmt, formatList); 314 | } 315 | ``` 316 | 317 | The construction of a `FormatList` instance is very lightweight - it defers 318 | all formatting and simply stores a couple of function pointers and a value 319 | pointer per argument. Since most of the actual work is done inside 320 | `vformat()`, any logic which causes an early exit of `errorImpl()` - 321 | filtering of verbose log messages based on error code for example - could be a 322 | useful optimization for programs using tinyformat. (A faster option would be 323 | to write any early bailout code inside `error()`, though this must be done in 324 | the header.) 325 | 326 | 327 | ## Benchmarks 328 | 329 | ### Compile time and code bloat 330 | 331 | The script `bloat_test.sh` included in the repository tests whether 332 | tinyformat succeeds in avoiding compile time and code bloat for nontrivial 333 | projects. The idea is to include `tinyformat.h` into 100 translation units 334 | and use `printf()` five times in each to simulate a medium sized project. 335 | The resulting executable size and compile time (g++-4.8.2, linux ubuntu 14.04) 336 | is shown in the following tables, which can be regenerated using `make 337 | bloat_test`: 338 | 339 | **Non-optimized build** 340 | 341 | | test name | compiler wall time | executable size (stripped) | 342 | | ---------------------- | ------------------ | -------------------------- | 343 | | libc printf | 1.8s | 48K (36K) | 344 | | std::ostream | 10.7s | 96K (76K) | 345 | | tinyformat, no inlines | 18.9s | 140K (104K) | 346 | | tinyformat | 21.1s | 220K (180K) | 347 | | tinyformat, c++0x mode | 20.7s | 220K (176K) | 348 | | boost::format | 70.1s | 844K (736K) | 349 | 350 | **Optimized build (-O3 -DNDEBUG)** 351 | 352 | | test name | compiler wall time | executable size (stripped) | 353 | | ---------------------- | ------------------ | -------------------------- | 354 | | libc printf | 2.3s | 40K (28K) | 355 | | std::ostream | 11.8s | 104K (80K) | 356 | | tinyformat, no inlines | 23.0s | 128K (104K) | 357 | | tinyformat | 32.9s | 128K (104K) | 358 | | tinyformat, c++0x mode | 34.0s | 128K (104K) | 359 | | boost::format | 147.9s | 644K (600K) | 360 | 361 | For large projects it's arguably worthwhile to do separate compilation of the 362 | non-templated parts of tinyformat, as shown in the rows labelled *tinyformat, 363 | no inlines*. These were generated by putting the implementation of `vformat` 364 | (`detail::formatImpl()` etc) it into a separate file, tinyformat.cpp. Note 365 | that the results above can vary considerably with different compilers. For 366 | example, the `-fipa-cp-clone` optimization pass in g++-4.6 resulted in 367 | excessively large binaries. On the other hand, the g++-4.8 results are quite 368 | similar to using clang++-3.4. 369 | 370 | 371 | ### Speed tests 372 | 373 | The following speed tests results were generated by building 374 | `tinyformat_speed_test.cpp` on an Intel core i7-2600K running Linux Ubuntu 375 | 14.04 with g++-4.8.2 using `-O3 -DNDEBUG`. In the test, the format string 376 | `"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` is filled 2000000 times with output sent to 377 | `/dev/null`; for further details see the source and Makefile. 378 | 379 | | test name | run time | 380 | | -------------- | -------- | 381 | | libc printf | 1.20s | 382 | | std::ostream | 1.82s | 383 | | tinyformat | 2.08s | 384 | | boost::format | 9.04s | 385 | 386 | It's likely that tinyformat has an advantage over boost.format because it tries 387 | reasonably hard to avoid formatting into temporary strings, preferring instead 388 | to send the results directly to the stream buffer. Tinyformat cannot 389 | be faster than the iostreams because it uses them internally, but it comes 390 | acceptably close. 391 | 392 | 393 | ## Rationale 394 | 395 | Or, why did I reinvent this particularly well studied wheel? 396 | 397 | Nearly every program needs text formatting in some form but in many cases such 398 | formatting is *incidental* to the main purpose of the program. In these cases, 399 | you really want a library which is simple to use but as lightweight as 400 | possible. 401 | 402 | The ultimate in lightweight dependencies are the solutions provided by the C++ 403 | and C libraries. However, both the C++ iostreams and C's printf() have 404 | well known usability problems: iostreams are hopelessly verbose for complicated 405 | formatting and printf() lacks extensibility and type safety. For example: 406 | 407 | ```C++ 408 | // Verbose; hard to read, hard to type: 409 | std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n"; 410 | // The alternative using a format string is much easier on the eyes 411 | tfm::printf("%.2f\n", 1.23456); 412 | 413 | // Type mismatch between "%s" and int: will cause a segfault at runtime! 414 | printf("%s", 1); 415 | // The following is perfectly fine, and will result in "1" being printed. 416 | tfm::printf("%s", 1); 417 | ``` 418 | 419 | On the other hand, there are plenty of excellent and complete libraries which 420 | solve the formatting problem in great generality (boost.format and fastformat 421 | come to mind, but there are many others). Unfortunately these kind of 422 | libraries tend to be rather heavy dependencies, far too heavy for projects 423 | which need to do only a little formatting. Problems include 424 | 425 | 1. Having many large source files. This makes a heavy dependency unsuitable to 426 | bundle within other projects for convenience. 427 | 2. Slow build times for every file using any sort of formatting (this is very 428 | noticeable with g++ and boost/format.hpp. I'm not sure about the various 429 | other alternatives.) 430 | 3. Code bloat due to instantiating many templates 431 | 432 | Tinyformat tries to solve these problems while providing formatting which is 433 | sufficiently general and fast for incidental day to day uses. 434 | 435 | 436 | ## License 437 | 438 | For minimum license-related fuss, tinyformat.h is distributed under the boost 439 | software license, version 1.0. (Summary: you must keep the license text on 440 | all source copies, but don't have to mention tinyformat when distributing 441 | binaries.) 442 | 443 | 444 | ## Author and acknowledgements 445 | 446 | Tinyformat is written and maintained by Chris Foster, with various contributions 447 | gratefully recieved [from the community](https://github.com/c42f/tinyformat/graphs/contributors). 448 | 449 | Originally the implementation was inspired by the way `boost::format` uses 450 | stream based formatting to simulate most of the `printf()` syntax, and Douglas 451 | Gregor's toy `printf()` in an [early variadic template example](https://web.archive.org/web/20131018185034/http://www.generic-programming.org/~dgregor/cpp/variadic-templates.html). 452 | 453 | ## Bugs 454 | 455 | Here's a list of known bugs which are probably cumbersome to fix: 456 | 457 | * Field padding won't work correctly with complicated user defined types. For 458 | general types, the only way to do this correctly seems to be format to a 459 | temporary string stream, check the length, and finally send to the output 460 | stream with padding if necessary. Doing this for all types would be 461 | quite inelegant because it implies extra allocations to make the temporary 462 | stream. A workaround is to add logic to `operator<<()` for composite user 463 | defined types so they are aware of the stream field width. 464 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Windows CI build via the Appveyor service 2 | 3 | version: '{build}' 4 | 5 | # branches to build 6 | branches: 7 | only: 8 | - master 9 | 10 | # scripts that are called at very beginning, before repo cloning 11 | init: 12 | - git config --global core.autocrlf input 13 | 14 | # Build matrix variables - 15 | # * environment 16 | # * platform 17 | # * configuration 18 | environment: 19 | matrix: 20 | - COMPILER: "Visual Studio 12 Win64" 21 | - COMPILER: "Visual Studio 14 Win64" 22 | - COMPILER: "Visual Studio 15 Win64" 23 | APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 24 | 25 | # build platform, i.e. x86, x64, Any CPU. 26 | platform: 27 | - x86 28 | - x64 29 | 30 | # build Configuration, i.e. Debug, Release, etc. 31 | configuration: 32 | - Debug 33 | - Release 34 | 35 | before_build: 36 | - echo -------------------------------------------------------------------------------- 37 | - echo Appveyor environment info: 38 | - echo COMPILER = %COMPILER%, CONFIGURATION = %CONFIGURATION% 39 | - cmake -h 40 | 41 | build_script: 42 | - echo -------------------------------------------------------------------------------- 43 | - echo Build tinyformat 44 | - mkdir build 45 | - cd build 46 | - cmake -G "%COMPILER%" .. 47 | - cmake --build . --config %CONFIGURATION% 48 | 49 | test_script: 50 | # cmake testall target has problems finding the correct configuration, so use ctest directly. 51 | - ctest -VV -C %CONFIGURATION% 52 | 53 | -------------------------------------------------------------------------------- /bloat_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script to test how much bloating a large project will suffer when using 4 | # tinyformat, vs alternatives. Call as 5 | # 6 | # C99 printf : bloat_test.sh $CXX [-O3] 7 | # tinyformat : bloat_test.sh $CXX [-O3] -DUSE_TINYFORMAT 8 | # tinyformat, no inlines: bloat_test.sh $CXX [-O3] -DUSE_TINYFORMAT -DUSE_TINYFORMAT_NOINLINE 9 | # boost::format : bloat_test.sh $CXX [-O3] -DUSE_BOOST 10 | # std::iostream : bloat_test.sh $CXX [-O3] -DUSE_IOSTREAMS 11 | # 12 | # Note: to test the NOINLINE version of tinyformat, you need to remove the few 13 | # inline functions in the tinyformat::detail namespace, and put them into a 14 | # file tinyformat.cpp. Then rename that version of tinyformat.h into 15 | # tinyformat_noinline.h 16 | 17 | 18 | prefix=_bloat_test_tmp_ 19 | numTranslationUnits=100 20 | 21 | rm -f $prefix??.cpp ${prefix}main.cpp ${prefix}all.h 22 | 23 | template=' 24 | #ifdef USE_BOOST 25 | 26 | #include 27 | #include 28 | 29 | void doFormat_a() 30 | { 31 | std::cout << boost::format("%s\n") % "somefile.cpp"; 32 | std::cout << boost::format("%s:%d\n") % "somefile.cpp"% 42; 33 | std::cout << boost::format("%s:%d:%s\n") % "somefile.cpp"% 42% "asdf"; 34 | std::cout << boost::format("%s:%d:%d:%s\n") % "somefile.cpp"% 42% 1% "asdf"; 35 | std::cout << boost::format("%s:%d:%d:%d:%s\n") % "somefile.cpp"% 42% 1% 2% "asdf"; 36 | } 37 | 38 | #elif defined(USE_IOSTREAMS) 39 | 40 | #include 41 | 42 | void doFormat_a() 43 | { 44 | const char* str1 = "somefile.cpp"; 45 | const char* str2 = "asdf"; 46 | std::cout << str1 << "\n"; 47 | std::cout << str1 << ":" << 42 << "\n"; 48 | std::cout << str1 << ":" << 42 << ":" << str2 << "\n"; 49 | std::cout << str1 << ":" << 42 << ":" << 1 << ":" << str2 << "\n"; 50 | std::cout << str1 << ":" << 42 << ":" << 1 << ":" << 2 << ":" << str2 << "\n"; 51 | } 52 | 53 | #else 54 | #ifdef USE_TINYFORMAT 55 | # ifdef USE_TINYFORMAT_NOINLINE 56 | # include "tinyformat_noinline.h" 57 | # else 58 | # include "tinyformat.h" 59 | # endif 60 | # define PRINTF tfm::printf 61 | #else 62 | # include 63 | # define PRINTF ::printf 64 | #endif 65 | 66 | void doFormat_a() 67 | { 68 | const char* str1 = "somefile.cpp"; 69 | const char* str2 = "asdf"; 70 | PRINTF("%s\n", str1); 71 | PRINTF("%s:%d\n", str1, 42); 72 | PRINTF("%s:%d:%s\n", str1, 42, str2); 73 | PRINTF("%s:%d:%d:%s\n", str1, 42, 1, str2); 74 | PRINTF("%s:%d:%d:%d:%s\n", str1, 42, 1, 2, str2); 75 | } 76 | #endif 77 | ' 78 | 79 | # Generate all the files 80 | echo "#include \"${prefix}all.h\"" >> ${prefix}main.cpp 81 | echo ' 82 | #ifdef USE_TINYFORMAT_NOINLINE 83 | #include "tinyformat.cpp" 84 | #endif 85 | 86 | int main() 87 | {' >> ${prefix}main.cpp 88 | 89 | for ((i=0;i<$numTranslationUnits;i++)) ; do 90 | n=$(printf "%03d" $i) 91 | f=${prefix}$n.cpp 92 | echo "$template" | sed -e "s/doFormat_a/doFormat_a$n/" -e "s/42/$i/" > $f 93 | echo "doFormat_a$n();" >> ${prefix}main.cpp 94 | echo "void doFormat_a$n();" >> ${prefix}all.h 95 | done 96 | 97 | echo "return 0; }" >> ${prefix}main.cpp 98 | 99 | 100 | # Compile 101 | time "$@" ${prefix}???.cpp ${prefix}main.cpp -o ${prefix}.out 102 | ls -sh ${prefix}.out 103 | cp ${prefix}.out ${prefix}stripped.out 104 | strip ${prefix}stripped.out 105 | ls -sh ${prefix}stripped.out 106 | -------------------------------------------------------------------------------- /tinyformat.h: -------------------------------------------------------------------------------- 1 | // tinyformat.h 2 | // Copyright (C) 2011, Chris Foster [chris42f (at) gmail (d0t) com] 3 | // 4 | // Boost Software License - Version 1.0 5 | // 6 | // Permission is hereby granted, free of charge, to any person or organization 7 | // obtaining a copy of the software and accompanying documentation covered by 8 | // this license (the "Software") to use, reproduce, display, distribute, 9 | // execute, and transmit the Software, and to prepare derivative works of the 10 | // Software, and to permit third-parties to whom the Software is furnished to 11 | // do so, all subject to the following: 12 | // 13 | // The copyright notices in the Software and this entire statement, including 14 | // the above license grant, this restriction and the following disclaimer, 15 | // must be included in all copies of the Software, in whole or in part, and 16 | // all derivative works of the Software, unless such copies or derivative 17 | // works are solely in the form of machine-executable object code generated by 18 | // a source language processor. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 | // SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 | // FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | // DEALINGS IN THE SOFTWARE. 27 | 28 | //------------------------------------------------------------------------------ 29 | // Tinyformat: A minimal type safe printf replacement 30 | // 31 | // tinyformat.h is a type safe printf replacement library in a single C++ 32 | // header file. Design goals include: 33 | // 34 | // * Type safety and extensibility for user defined types. 35 | // * C99 printf() compatibility, to the extent possible using std::ostream 36 | // * POSIX extension for positional arguments 37 | // * Simplicity and minimalism. A single header file to include and distribute 38 | // with your projects. 39 | // * Augment rather than replace the standard stream formatting mechanism 40 | // * C++98 support, with optional C++11 niceties 41 | // 42 | // 43 | // Main interface example usage 44 | // ---------------------------- 45 | // 46 | // To print a date to std::cout for American usage: 47 | // 48 | // std::string weekday = "Wednesday"; 49 | // const char* month = "July"; 50 | // size_t day = 27; 51 | // long hour = 14; 52 | // int min = 44; 53 | // 54 | // tfm::printf("%s, %s %d, %.2d:%.2d\n", weekday, month, day, hour, min); 55 | // 56 | // POSIX extension for positional arguments is available. 57 | // The ability to rearrange formatting arguments is an important feature 58 | // for localization because the word order may vary in different languages. 59 | // 60 | // Previous example for German usage. Arguments are reordered: 61 | // 62 | // tfm::printf("%1$s, %3$d. %2$s, %4$d:%5$.2d\n", weekday, month, day, hour, min); 63 | // 64 | // The strange types here emphasize the type safety of the interface; it is 65 | // possible to print a std::string using the "%s" conversion, and a 66 | // size_t using the "%d" conversion. A similar result could be achieved 67 | // using either of the tfm::format() functions. One prints on a user provided 68 | // stream: 69 | // 70 | // tfm::format(std::cerr, "%s, %s %d, %.2d:%.2d\n", 71 | // weekday, month, day, hour, min); 72 | // 73 | // The other returns a std::string: 74 | // 75 | // std::string date = tfm::format("%s, %s %d, %.2d:%.2d\n", 76 | // weekday, month, day, hour, min); 77 | // std::cout << date; 78 | // 79 | // These are the three primary interface functions. There is also a 80 | // convenience function printfln() which appends a newline to the usual result 81 | // of printf() for super simple logging. 82 | // 83 | // 84 | // User defined format functions 85 | // ----------------------------- 86 | // 87 | // Simulating variadic templates in C++98 is pretty painful since it requires 88 | // writing out the same function for each desired number of arguments. To make 89 | // this bearable tinyformat comes with a set of macros which are used 90 | // internally to generate the API, but which may also be used in user code. 91 | // 92 | // The three macros TINYFORMAT_ARGTYPES(n), TINYFORMAT_VARARGS(n) and 93 | // TINYFORMAT_PASSARGS(n) will generate a list of n argument types, 94 | // type/name pairs and argument names respectively when called with an integer 95 | // n between 1 and 16. We can use these to define a macro which generates the 96 | // desired user defined function with n arguments. To generate all 16 user 97 | // defined function bodies, use the macro TINYFORMAT_FOREACH_ARGNUM. For an 98 | // example, see the implementation of printf() at the end of the source file. 99 | // 100 | // Sometimes it's useful to be able to pass a list of format arguments through 101 | // to a non-template function. The FormatList class is provided as a way to do 102 | // this by storing the argument list in a type-opaque way. Continuing the 103 | // example from above, we construct a FormatList using makeFormatList(): 104 | // 105 | // FormatListRef formatList = tfm::makeFormatList(weekday, month, day, hour, min); 106 | // 107 | // The format list can now be passed into any non-template function and used 108 | // via a call to the vformat() function: 109 | // 110 | // tfm::vformat(std::cout, "%s, %s %d, %.2d:%.2d\n", formatList); 111 | // 112 | // 113 | // Additional API information 114 | // -------------------------- 115 | // 116 | // Error handling: Define TINYFORMAT_ERROR to customize the error handling for 117 | // format strings which are unsupported or have the wrong number of format 118 | // specifiers (calls assert() by default). 119 | // 120 | // User defined types: Uses operator<< for user defined types by default. 121 | // Overload formatValue() for more control. 122 | 123 | 124 | #ifndef TINYFORMAT_H_INCLUDED 125 | #define TINYFORMAT_H_INCLUDED 126 | 127 | namespace tinyformat {} 128 | //------------------------------------------------------------------------------ 129 | // Config section. Customize to your liking! 130 | 131 | // Namespace alias to encourage brevity 132 | namespace tfm = tinyformat; 133 | 134 | // Error handling; calls assert() by default. 135 | // #define TINYFORMAT_ERROR(reasonString) your_error_handler(reasonString) 136 | 137 | // Define for C++11 variadic templates which make the code shorter & more 138 | // general. If you don't define this, C++11 support is autodetected below. 139 | // #define TINYFORMAT_USE_VARIADIC_TEMPLATES 140 | 141 | 142 | //------------------------------------------------------------------------------ 143 | // Implementation details. 144 | #include 145 | #include 146 | #include 147 | #include 148 | 149 | #ifndef TINYFORMAT_ASSERT 150 | # include 151 | # define TINYFORMAT_ASSERT(cond) assert(cond) 152 | #endif 153 | 154 | #ifndef TINYFORMAT_ERROR 155 | # include 156 | # define TINYFORMAT_ERROR(reason) assert(0 && reason) 157 | #endif 158 | 159 | #if !defined(TINYFORMAT_USE_VARIADIC_TEMPLATES) && !defined(TINYFORMAT_NO_VARIADIC_TEMPLATES) 160 | # ifdef __GXX_EXPERIMENTAL_CXX0X__ 161 | # define TINYFORMAT_USE_VARIADIC_TEMPLATES 162 | # endif 163 | #endif 164 | 165 | #if defined(__GLIBCXX__) && __GLIBCXX__ < 20080201 166 | // std::showpos is broken on old libstdc++ as provided with macOS. See 167 | // http://gcc.gnu.org/ml/libstdc++/2007-11/msg00075.html 168 | # define TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND 169 | #endif 170 | 171 | #ifdef __APPLE__ 172 | // Workaround macOS linker warning: Xcode uses different default symbol 173 | // visibilities for static libs vs executables (see issue #25) 174 | # define TINYFORMAT_HIDDEN __attribute__((visibility("hidden"))) 175 | #else 176 | # define TINYFORMAT_HIDDEN 177 | #endif 178 | 179 | namespace tinyformat { 180 | 181 | //------------------------------------------------------------------------------ 182 | namespace detail { 183 | 184 | // Test whether type T1 is convertible to type T2 185 | template 186 | struct is_convertible 187 | { 188 | private: 189 | // two types of different size 190 | struct fail { char dummy[2]; }; 191 | struct succeed { char dummy; }; 192 | // Try to convert a T1 to a T2 by plugging into tryConvert 193 | static fail tryConvert(...); 194 | static succeed tryConvert(const T2&); 195 | static const T1& makeT1(); 196 | public: 197 | # ifdef _MSC_VER 198 | // Disable spurious loss of precision warnings in tryConvert(makeT1()) 199 | # pragma warning(push) 200 | # pragma warning(disable:4244) 201 | # pragma warning(disable:4267) 202 | # endif 203 | // Standard trick: the (...) version of tryConvert will be chosen from 204 | // the overload set only if the version taking a T2 doesn't match. 205 | // Then we compare the sizes of the return types to check which 206 | // function matched. Very neat, in a disgusting kind of way :) 207 | static const bool value = 208 | sizeof(tryConvert(makeT1())) == sizeof(succeed); 209 | # ifdef _MSC_VER 210 | # pragma warning(pop) 211 | # endif 212 | }; 213 | 214 | 215 | // Detect when a type is not a wchar_t string 216 | template struct is_wchar { typedef int tinyformat_wchar_is_not_supported; }; 217 | template<> struct is_wchar {}; 218 | template<> struct is_wchar {}; 219 | template struct is_wchar {}; 220 | template struct is_wchar {}; 221 | 222 | 223 | // Format the value by casting to type fmtT. This default implementation 224 | // should never be called. 225 | template::value> 226 | struct formatValueAsType 227 | { 228 | static void invoke(std::ostream& /*out*/, const T& /*value*/) { TINYFORMAT_ASSERT(0); } 229 | }; 230 | // Specialized version for types that can actually be converted to fmtT, as 231 | // indicated by the "convertible" template parameter. 232 | template 233 | struct formatValueAsType 234 | { 235 | static void invoke(std::ostream& out, const T& value) 236 | { out << static_cast(value); } 237 | }; 238 | 239 | #ifdef TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND 240 | template::value> 241 | struct formatZeroIntegerWorkaround 242 | { 243 | static bool invoke(std::ostream& /**/, const T& /**/) { return false; } 244 | }; 245 | template 246 | struct formatZeroIntegerWorkaround 247 | { 248 | static bool invoke(std::ostream& out, const T& value) 249 | { 250 | if (static_cast(value) == 0 && out.flags() & std::ios::showpos) { 251 | out << "+0"; 252 | return true; 253 | } 254 | return false; 255 | } 256 | }; 257 | #endif // TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND 258 | 259 | // Convert an arbitrary type to integer. The version with convertible=false 260 | // throws an error. 261 | template::value> 262 | struct convertToInt 263 | { 264 | static int invoke(const T& /*value*/) 265 | { 266 | TINYFORMAT_ERROR("tinyformat: Cannot convert from argument type to " 267 | "integer for use as variable width or precision"); 268 | return 0; 269 | } 270 | }; 271 | // Specialization for convertToInt when conversion is possible 272 | template 273 | struct convertToInt 274 | { 275 | static int invoke(const T& value) { return static_cast(value); } 276 | }; 277 | 278 | // Format at most ntrunc characters to the given stream. 279 | template 280 | inline void formatTruncated(std::ostream& out, const T& value, int ntrunc) 281 | { 282 | std::ostringstream tmp; 283 | tmp << value; 284 | std::string result = tmp.str(); 285 | out.write(result.c_str(), (std::min)(ntrunc, static_cast(result.size()))); 286 | } 287 | #define TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(type) \ 288 | inline void formatTruncated(std::ostream& out, type* value, int ntrunc) \ 289 | { \ 290 | std::streamsize len = 0; \ 291 | while (len < ntrunc && value[len] != 0) \ 292 | ++len; \ 293 | out.write(value, len); \ 294 | } 295 | // Overload for const char* and char*. Could overload for signed & unsigned 296 | // char too, but these are technically unneeded for printf compatibility. 297 | TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(const char) 298 | TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(char) 299 | #undef TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR 300 | 301 | template 302 | void spaceFillIfNotFinite(std::ostream& out, const T& value) { } 303 | // TODO: type_traits would clearly be better here. Should consider moving all 304 | // these workarounds into a big pre-C++11 section. 305 | #define TINYFORMAT_SETFILL_NOT_FINITE_FLOATING(type) \ 306 | inline void spaceFillIfNotFinite(std::ostream& out, type value) \ 307 | { \ 308 | if (out.fill() == '0' && !std::isfinite(value)) \ 309 | out.fill(' '); \ 310 | } 311 | TINYFORMAT_SETFILL_NOT_FINITE_FLOATING(float) 312 | TINYFORMAT_SETFILL_NOT_FINITE_FLOATING(double) 313 | TINYFORMAT_SETFILL_NOT_FINITE_FLOATING(long double) 314 | #undef TINYFORMAT_SETFILL_NOT_FINITE_FLOATING 315 | 316 | } // namespace detail 317 | 318 | 319 | //------------------------------------------------------------------------------ 320 | // Variable formatting functions. May be overridden for user-defined types if 321 | // desired. 322 | 323 | 324 | /// Format a value into a stream, delegating to operator<< by default. 325 | /// 326 | /// Users may override this for their own types. When this function is called, 327 | /// the stream flags will have been modified according to the format string. 328 | /// The format specification is provided in the range [fmtBegin, fmtEnd). For 329 | /// truncating conversions, ntrunc is set to the desired maximum number of 330 | /// characters, for example "%.7s" calls formatValue with ntrunc = 7. 331 | /// 332 | /// By default, formatValue() uses the usual stream insertion operator 333 | /// operator<< to format the type T, with special cases for the %c and %p 334 | /// conversions. 335 | template 336 | inline void formatValue(std::ostream& out, const char* /*fmtBegin*/, 337 | const char* fmtEnd, int ntrunc, const T& value) 338 | { 339 | #ifndef TINYFORMAT_ALLOW_WCHAR_STRINGS 340 | // Since we don't support printing of wchar_t using "%ls", make it fail at 341 | // compile time in preference to printing as a void* at runtime. 342 | typedef typename detail::is_wchar::tinyformat_wchar_is_not_supported DummyType; 343 | (void) DummyType(); // avoid unused type warning with gcc-4.8 344 | #endif 345 | // The mess here is to support the %c and %p conversions: if these 346 | // conversions are active we try to convert the type to a char or const 347 | // void* respectively and format that instead of the value itself. For the 348 | // %p conversion it's important to avoid dereferencing the pointer, which 349 | // could otherwise lead to a crash when printing a dangling (const char*). 350 | const bool canConvertToChar = detail::is_convertible::value; 351 | const bool canConvertToVoidPtr = detail::is_convertible::value; 352 | detail::spaceFillIfNotFinite(out, value); 353 | if (canConvertToChar && *(fmtEnd-1) == 'c') 354 | detail::formatValueAsType::invoke(out, value); 355 | else if (canConvertToVoidPtr && *(fmtEnd-1) == 'p') 356 | detail::formatValueAsType::invoke(out, value); 357 | #ifdef TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND 358 | else if (detail::formatZeroIntegerWorkaround::invoke(out, value)) /**/; 359 | #endif 360 | else if (ntrunc >= 0) { 361 | // Take care not to overread C strings in truncating conversions like 362 | // "%.4s" where at most 4 characters may be read. 363 | detail::formatTruncated(out, value, ntrunc); 364 | } 365 | else 366 | out << value; 367 | } 368 | 369 | 370 | // Overloaded version for char types to support printing as an integer 371 | #define TINYFORMAT_DEFINE_FORMATVALUE_CHAR(charType) \ 372 | inline void formatValue(std::ostream& out, const char* /*fmtBegin*/, \ 373 | const char* fmtEnd, int /**/, charType value) \ 374 | { \ 375 | switch (*(fmtEnd-1)) { \ 376 | case 'u': case 'd': case 'i': case 'o': case 'X': case 'x': \ 377 | out << static_cast(value); break; \ 378 | default: \ 379 | out << value; break; \ 380 | } \ 381 | } 382 | // per 3.9.1: char, signed char and unsigned char are all distinct types 383 | TINYFORMAT_DEFINE_FORMATVALUE_CHAR(char) 384 | TINYFORMAT_DEFINE_FORMATVALUE_CHAR(signed char) 385 | TINYFORMAT_DEFINE_FORMATVALUE_CHAR(unsigned char) 386 | #undef TINYFORMAT_DEFINE_FORMATVALUE_CHAR 387 | 388 | 389 | //------------------------------------------------------------------------------ 390 | // Tools for emulating variadic templates in C++98. The basic idea here is 391 | // stolen from the boost preprocessor metaprogramming library and cut down to 392 | // be just general enough for what we need. 393 | 394 | #define TINYFORMAT_ARGTYPES(n) TINYFORMAT_ARGTYPES_ ## n 395 | #define TINYFORMAT_VARARGS(n) TINYFORMAT_VARARGS_ ## n 396 | #define TINYFORMAT_PASSARGS(n) TINYFORMAT_PASSARGS_ ## n 397 | #define TINYFORMAT_PASSARGS_TAIL(n) TINYFORMAT_PASSARGS_TAIL_ ## n 398 | 399 | // To keep it as transparent as possible, the macros below have been generated 400 | // using python via the excellent cog code generation script. This avoids 401 | // the need for a bunch of complex (but more general) preprocessor tricks as 402 | // used in boost.preprocessor. 403 | // 404 | // To rerun the code generation in place, use `cog -r tinyformat.h` 405 | // (see http://nedbatchelder.com/code/cog). Alternatively you can just create 406 | // extra versions by hand. 407 | 408 | /*[[[cog 409 | maxParams = 16 410 | 411 | def makeCommaSepLists(lineTemplate, elemTemplate, startInd=1): 412 | for j in range(startInd,maxParams+1): 413 | list = ', '.join([elemTemplate % {'i':i} for i in range(startInd,j+1)]) 414 | cog.outl(lineTemplate % {'j':j, 'list':list}) 415 | 416 | makeCommaSepLists('#define TINYFORMAT_ARGTYPES_%(j)d %(list)s', 417 | 'class T%(i)d') 418 | 419 | cog.outl() 420 | makeCommaSepLists('#define TINYFORMAT_VARARGS_%(j)d %(list)s', 421 | 'const T%(i)d& v%(i)d') 422 | 423 | cog.outl() 424 | makeCommaSepLists('#define TINYFORMAT_PASSARGS_%(j)d %(list)s', 'v%(i)d') 425 | 426 | cog.outl() 427 | cog.outl('#define TINYFORMAT_PASSARGS_TAIL_1') 428 | makeCommaSepLists('#define TINYFORMAT_PASSARGS_TAIL_%(j)d , %(list)s', 429 | 'v%(i)d', startInd = 2) 430 | 431 | cog.outl() 432 | cog.outl('#define TINYFORMAT_FOREACH_ARGNUM(m) \\\n ' + 433 | ' '.join(['m(%d)' % (j,) for j in range(1,maxParams+1)])) 434 | ]]]*/ 435 | #define TINYFORMAT_ARGTYPES_1 class T1 436 | #define TINYFORMAT_ARGTYPES_2 class T1, class T2 437 | #define TINYFORMAT_ARGTYPES_3 class T1, class T2, class T3 438 | #define TINYFORMAT_ARGTYPES_4 class T1, class T2, class T3, class T4 439 | #define TINYFORMAT_ARGTYPES_5 class T1, class T2, class T3, class T4, class T5 440 | #define TINYFORMAT_ARGTYPES_6 class T1, class T2, class T3, class T4, class T5, class T6 441 | #define TINYFORMAT_ARGTYPES_7 class T1, class T2, class T3, class T4, class T5, class T6, class T7 442 | #define TINYFORMAT_ARGTYPES_8 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8 443 | #define TINYFORMAT_ARGTYPES_9 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9 444 | #define TINYFORMAT_ARGTYPES_10 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10 445 | #define TINYFORMAT_ARGTYPES_11 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11 446 | #define TINYFORMAT_ARGTYPES_12 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12 447 | #define TINYFORMAT_ARGTYPES_13 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13 448 | #define TINYFORMAT_ARGTYPES_14 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14 449 | #define TINYFORMAT_ARGTYPES_15 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15 450 | #define TINYFORMAT_ARGTYPES_16 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16 451 | 452 | #define TINYFORMAT_VARARGS_1 const T1& v1 453 | #define TINYFORMAT_VARARGS_2 const T1& v1, const T2& v2 454 | #define TINYFORMAT_VARARGS_3 const T1& v1, const T2& v2, const T3& v3 455 | #define TINYFORMAT_VARARGS_4 const T1& v1, const T2& v2, const T3& v3, const T4& v4 456 | #define TINYFORMAT_VARARGS_5 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5 457 | #define TINYFORMAT_VARARGS_6 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6 458 | #define TINYFORMAT_VARARGS_7 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7 459 | #define TINYFORMAT_VARARGS_8 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8 460 | #define TINYFORMAT_VARARGS_9 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9 461 | #define TINYFORMAT_VARARGS_10 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10 462 | #define TINYFORMAT_VARARGS_11 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11 463 | #define TINYFORMAT_VARARGS_12 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12 464 | #define TINYFORMAT_VARARGS_13 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13 465 | #define TINYFORMAT_VARARGS_14 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13, const T14& v14 466 | #define TINYFORMAT_VARARGS_15 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13, const T14& v14, const T15& v15 467 | #define TINYFORMAT_VARARGS_16 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13, const T14& v14, const T15& v15, const T16& v16 468 | 469 | #define TINYFORMAT_PASSARGS_1 v1 470 | #define TINYFORMAT_PASSARGS_2 v1, v2 471 | #define TINYFORMAT_PASSARGS_3 v1, v2, v3 472 | #define TINYFORMAT_PASSARGS_4 v1, v2, v3, v4 473 | #define TINYFORMAT_PASSARGS_5 v1, v2, v3, v4, v5 474 | #define TINYFORMAT_PASSARGS_6 v1, v2, v3, v4, v5, v6 475 | #define TINYFORMAT_PASSARGS_7 v1, v2, v3, v4, v5, v6, v7 476 | #define TINYFORMAT_PASSARGS_8 v1, v2, v3, v4, v5, v6, v7, v8 477 | #define TINYFORMAT_PASSARGS_9 v1, v2, v3, v4, v5, v6, v7, v8, v9 478 | #define TINYFORMAT_PASSARGS_10 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10 479 | #define TINYFORMAT_PASSARGS_11 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 480 | #define TINYFORMAT_PASSARGS_12 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 481 | #define TINYFORMAT_PASSARGS_13 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13 482 | #define TINYFORMAT_PASSARGS_14 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14 483 | #define TINYFORMAT_PASSARGS_15 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 484 | #define TINYFORMAT_PASSARGS_16 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 485 | 486 | #define TINYFORMAT_PASSARGS_TAIL_1 487 | #define TINYFORMAT_PASSARGS_TAIL_2 , v2 488 | #define TINYFORMAT_PASSARGS_TAIL_3 , v2, v3 489 | #define TINYFORMAT_PASSARGS_TAIL_4 , v2, v3, v4 490 | #define TINYFORMAT_PASSARGS_TAIL_5 , v2, v3, v4, v5 491 | #define TINYFORMAT_PASSARGS_TAIL_6 , v2, v3, v4, v5, v6 492 | #define TINYFORMAT_PASSARGS_TAIL_7 , v2, v3, v4, v5, v6, v7 493 | #define TINYFORMAT_PASSARGS_TAIL_8 , v2, v3, v4, v5, v6, v7, v8 494 | #define TINYFORMAT_PASSARGS_TAIL_9 , v2, v3, v4, v5, v6, v7, v8, v9 495 | #define TINYFORMAT_PASSARGS_TAIL_10 , v2, v3, v4, v5, v6, v7, v8, v9, v10 496 | #define TINYFORMAT_PASSARGS_TAIL_11 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 497 | #define TINYFORMAT_PASSARGS_TAIL_12 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 498 | #define TINYFORMAT_PASSARGS_TAIL_13 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13 499 | #define TINYFORMAT_PASSARGS_TAIL_14 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14 500 | #define TINYFORMAT_PASSARGS_TAIL_15 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 501 | #define TINYFORMAT_PASSARGS_TAIL_16 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 502 | 503 | #define TINYFORMAT_FOREACH_ARGNUM(m) \ 504 | m(1) m(2) m(3) m(4) m(5) m(6) m(7) m(8) m(9) m(10) m(11) m(12) m(13) m(14) m(15) m(16) 505 | //[[[end]]] 506 | 507 | 508 | 509 | namespace detail { 510 | 511 | // Type-opaque holder for an argument to format(), with associated actions on 512 | // the type held as explicit function pointers. This allows FormatArg's for 513 | // each argument to be allocated as a homogeneous array inside FormatList 514 | // whereas a naive implementation based on inheritance does not. 515 | class FormatArg 516 | { 517 | public: 518 | FormatArg() 519 | : m_value(NULL), 520 | m_formatImpl(NULL), 521 | m_toIntImpl(NULL) 522 | { } 523 | 524 | template 525 | FormatArg(const T& value) 526 | // C-style cast here allows us to also remove volatile; we put it 527 | // back in the *Impl functions before dereferencing to avoid UB. 528 | : m_value((const void*)(&value)), 529 | m_formatImpl(&formatImpl), 530 | m_toIntImpl(&toIntImpl) 531 | { } 532 | 533 | void format(std::ostream& out, const char* fmtBegin, 534 | const char* fmtEnd, int ntrunc) const 535 | { 536 | TINYFORMAT_ASSERT(m_value); 537 | TINYFORMAT_ASSERT(m_formatImpl); 538 | m_formatImpl(out, fmtBegin, fmtEnd, ntrunc, m_value); 539 | } 540 | 541 | int toInt() const 542 | { 543 | TINYFORMAT_ASSERT(m_value); 544 | TINYFORMAT_ASSERT(m_toIntImpl); 545 | return m_toIntImpl(m_value); 546 | } 547 | 548 | private: 549 | template 550 | TINYFORMAT_HIDDEN static void formatImpl(std::ostream& out, const char* fmtBegin, 551 | const char* fmtEnd, int ntrunc, const void* value) 552 | { 553 | formatValue(out, fmtBegin, fmtEnd, ntrunc, *static_cast(value)); 554 | } 555 | 556 | template 557 | TINYFORMAT_HIDDEN static int toIntImpl(const void* value) 558 | { 559 | return convertToInt::invoke(*static_cast(value)); 560 | } 561 | 562 | const void* m_value; 563 | void (*m_formatImpl)(std::ostream& out, const char* fmtBegin, 564 | const char* fmtEnd, int ntrunc, const void* value); 565 | int (*m_toIntImpl)(const void* value); 566 | }; 567 | 568 | 569 | // Parse and return an integer from the string c, as atoi() 570 | // On return, c is set to one past the end of the integer. 571 | inline int parseIntAndAdvance(const char*& c) 572 | { 573 | int i = 0; 574 | for (;*c >= '0' && *c <= '9'; ++c) 575 | i = 10*i + (*c - '0'); 576 | return i; 577 | } 578 | 579 | // Parse width or precision `n` from format string pointer `c`, and advance it 580 | // to the next character. If an indirection is requested with `*`, the argument 581 | // is read from `args[argIndex]` and `argIndex` is incremented (or read 582 | // from `args[n]` in positional mode). Returns true if one or more 583 | // characters were read. 584 | inline bool parseWidthOrPrecision(int& n, const char*& c, bool positionalMode, 585 | const detail::FormatArg* args, 586 | int& argIndex, int numArgs) 587 | { 588 | if (*c >= '0' && *c <= '9') { 589 | n = parseIntAndAdvance(c); 590 | } 591 | else if (*c == '*') { 592 | ++c; 593 | n = 0; 594 | if (positionalMode) { 595 | int pos = parseIntAndAdvance(c) - 1; 596 | if (*c != '$') 597 | TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one"); 598 | if (pos >= 0 && pos < numArgs) 599 | n = args[pos].toInt(); 600 | else 601 | TINYFORMAT_ERROR("tinyformat: Positional argument out of range"); 602 | ++c; 603 | } 604 | else { 605 | if (argIndex < numArgs) 606 | n = args[argIndex++].toInt(); 607 | else 608 | TINYFORMAT_ERROR("tinyformat: Not enough arguments to read variable width or precision"); 609 | } 610 | } 611 | else { 612 | return false; 613 | } 614 | return true; 615 | } 616 | 617 | // Print literal part of format string and return next format spec position. 618 | // 619 | // Skips over any occurrences of '%%', printing a literal '%' to the output. 620 | // The position of the first % character of the next nontrivial format spec is 621 | // returned, or the end of string. 622 | inline const char* printFormatStringLiteral(std::ostream& out, const char* fmt) 623 | { 624 | const char* c = fmt; 625 | for (;; ++c) { 626 | if (*c == '\0') { 627 | out.write(fmt, c - fmt); 628 | return c; 629 | } 630 | else if (*c == '%') { 631 | out.write(fmt, c - fmt); 632 | if (*(c+1) != '%') 633 | return c; 634 | // for "%%", tack trailing % onto next literal section. 635 | fmt = ++c; 636 | } 637 | } 638 | } 639 | 640 | 641 | // Parse a format string and set the stream state accordingly. 642 | // 643 | // The format mini-language recognized here is meant to be the one from C99, 644 | // with the form "%[flags][width][.precision][length]type" with POSIX 645 | // positional arguments extension. 646 | // 647 | // POSIX positional arguments extension: 648 | // Conversions can be applied to the nth argument after the format in 649 | // the argument list, rather than to the next unused argument. In this case, 650 | // the conversion specifier character % (see below) is replaced by the sequence 651 | // "%n$", where n is a decimal integer in the range [1,{NL_ARGMAX}], 652 | // giving the position of the argument in the argument list. This feature 653 | // provides for the definition of format strings that select arguments 654 | // in an order appropriate to specific languages. 655 | // 656 | // The format can contain either numbered argument conversion specifications 657 | // (that is, "%n$" and "*m$"), or unnumbered argument conversion specifications 658 | // (that is, % and * ), but not both. The only exception to this is that %% 659 | // can be mixed with the "%n$" form. The results of mixing numbered and 660 | // unnumbered argument specifications in a format string are undefined. 661 | // When numbered argument specifications are used, specifying the Nth argument 662 | // requires that all the leading arguments, from the first to the (N-1)th, 663 | // are specified in the format string. 664 | // 665 | // In format strings containing the "%n$" form of conversion specification, 666 | // numbered arguments in the argument list can be referenced from the format 667 | // string as many times as required. 668 | // 669 | // Formatting options which can't be natively represented using the ostream 670 | // state are returned in spacePadPositive (for space padded positive numbers) 671 | // and ntrunc (for truncating conversions). argIndex is incremented if 672 | // necessary to pull out variable width and precision. The function returns a 673 | // pointer to the character after the end of the current format spec. 674 | inline const char* streamStateFromFormat(std::ostream& out, bool& positionalMode, 675 | bool& spacePadPositive, 676 | int& ntrunc, const char* fmtStart, 677 | const detail::FormatArg* args, 678 | int& argIndex, int numArgs) 679 | { 680 | TINYFORMAT_ASSERT(*fmtStart == '%'); 681 | // Reset stream state to defaults. 682 | out.width(0); 683 | out.precision(6); 684 | out.fill(' '); 685 | // Reset most flags; ignore irrelevant unitbuf & skipws. 686 | out.unsetf(std::ios::adjustfield | std::ios::basefield | 687 | std::ios::floatfield | std::ios::showbase | std::ios::boolalpha | 688 | std::ios::showpoint | std::ios::showpos | std::ios::uppercase); 689 | bool precisionSet = false; 690 | bool widthSet = false; 691 | int widthExtra = 0; 692 | const char* c = fmtStart + 1; 693 | 694 | // 1) Parse an argument index (if followed by '$') or a width possibly 695 | // preceded with '0' flag. 696 | if (*c >= '0' && *c <= '9') { 697 | const char tmpc = *c; 698 | int value = parseIntAndAdvance(c); 699 | if (*c == '$') { 700 | // value is an argument index 701 | if (value > 0 && value <= numArgs) 702 | argIndex = value - 1; 703 | else 704 | TINYFORMAT_ERROR("tinyformat: Positional argument out of range"); 705 | ++c; 706 | positionalMode = true; 707 | } 708 | else if (positionalMode) { 709 | TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one"); 710 | } 711 | else { 712 | if (tmpc == '0') { 713 | // Use internal padding so that numeric values are 714 | // formatted correctly, eg -00010 rather than 000-10 715 | out.fill('0'); 716 | out.setf(std::ios::internal, std::ios::adjustfield); 717 | } 718 | if (value != 0) { 719 | // Nonzero value means that we parsed width. 720 | widthSet = true; 721 | out.width(value); 722 | } 723 | } 724 | } 725 | else if (positionalMode) { 726 | TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one"); 727 | } 728 | // 2) Parse flags and width if we did not do it in previous step. 729 | if (!widthSet) { 730 | // Parse flags 731 | for (;; ++c) { 732 | switch (*c) { 733 | case '#': 734 | out.setf(std::ios::showpoint | std::ios::showbase); 735 | continue; 736 | case '0': 737 | // overridden by left alignment ('-' flag) 738 | if (!(out.flags() & std::ios::left)) { 739 | // Use internal padding so that numeric values are 740 | // formatted correctly, eg -00010 rather than 000-10 741 | out.fill('0'); 742 | out.setf(std::ios::internal, std::ios::adjustfield); 743 | } 744 | continue; 745 | case '-': 746 | out.fill(' '); 747 | out.setf(std::ios::left, std::ios::adjustfield); 748 | continue; 749 | case ' ': 750 | // overridden by show positive sign, '+' flag. 751 | if (!(out.flags() & std::ios::showpos)) 752 | spacePadPositive = true; 753 | continue; 754 | case '+': 755 | out.setf(std::ios::showpos); 756 | spacePadPositive = false; 757 | widthExtra = 1; 758 | continue; 759 | default: 760 | break; 761 | } 762 | break; 763 | } 764 | // Parse width 765 | int width = 0; 766 | widthSet = parseWidthOrPrecision(width, c, positionalMode, 767 | args, argIndex, numArgs); 768 | if (widthSet) { 769 | if (width < 0) { 770 | // negative widths correspond to '-' flag set 771 | out.fill(' '); 772 | out.setf(std::ios::left, std::ios::adjustfield); 773 | width = -width; 774 | } 775 | out.width(width); 776 | } 777 | } 778 | // 3) Parse precision 779 | if (*c == '.') { 780 | ++c; 781 | int precision = 0; 782 | parseWidthOrPrecision(precision, c, positionalMode, 783 | args, argIndex, numArgs); 784 | // Presence of `.` indicates precision set, unless the inferred value 785 | // was negative in which case the default is used. 786 | precisionSet = precision >= 0; 787 | if (precisionSet) 788 | out.precision(precision); 789 | } 790 | // 4) Ignore any C99 length modifier 791 | while (*c == 'l' || *c == 'h' || *c == 'L' || 792 | *c == 'j' || *c == 'z' || *c == 't') { 793 | ++c; 794 | } 795 | // 5) We're up to the conversion specifier character. 796 | // Set stream flags based on conversion specifier (thanks to the 797 | // boost::format class for forging the way here). 798 | bool intConversion = false; 799 | switch (*c) { 800 | case 'u': case 'd': case 'i': 801 | out.setf(std::ios::dec, std::ios::basefield); 802 | intConversion = true; 803 | break; 804 | case 'o': 805 | out.setf(std::ios::oct, std::ios::basefield); 806 | intConversion = true; 807 | break; 808 | case 'X': 809 | out.setf(std::ios::uppercase); 810 | // Falls through 811 | case 'x': case 'p': 812 | out.setf(std::ios::hex, std::ios::basefield); 813 | intConversion = true; 814 | break; 815 | case 'E': 816 | out.setf(std::ios::uppercase); 817 | // Falls through 818 | case 'e': 819 | out.setf(std::ios::scientific, std::ios::floatfield); 820 | out.setf(std::ios::dec, std::ios::basefield); 821 | break; 822 | case 'F': 823 | out.setf(std::ios::uppercase); 824 | // Falls through 825 | case 'f': 826 | out.setf(std::ios::fixed, std::ios::floatfield); 827 | break; 828 | case 'A': 829 | out.setf(std::ios::uppercase); 830 | // Falls through 831 | case 'a': 832 | # ifdef _MSC_VER 833 | // Workaround https://developercommunity.visualstudio.com/content/problem/520472/hexfloat-stream-output-does-not-ignore-precision-a.html 834 | // by always setting maximum precision on MSVC to avoid precision 835 | // loss for doubles. 836 | out.precision(13); 837 | # endif 838 | out.setf(std::ios::fixed | std::ios::scientific, std::ios::floatfield); 839 | break; 840 | case 'G': 841 | out.setf(std::ios::uppercase); 842 | // Falls through 843 | case 'g': 844 | out.setf(std::ios::dec, std::ios::basefield); 845 | // As in boost::format, let stream decide float format. 846 | out.flags(out.flags() & ~std::ios::floatfield); 847 | break; 848 | case 'c': 849 | // Handled as special case inside formatValue() 850 | break; 851 | case 's': 852 | if (precisionSet) 853 | ntrunc = static_cast(out.precision()); 854 | // Make %s print Booleans as "true" and "false" 855 | out.setf(std::ios::boolalpha); 856 | break; 857 | case 'n': 858 | // Not supported - will cause problems! 859 | TINYFORMAT_ERROR("tinyformat: %n conversion spec not supported"); 860 | break; 861 | case '\0': 862 | TINYFORMAT_ERROR("tinyformat: Conversion spec incorrectly " 863 | "terminated by end of string"); 864 | return c; 865 | default: 866 | break; 867 | } 868 | if (intConversion && precisionSet && !widthSet) { 869 | // "precision" for integers gives the minimum number of digits (to be 870 | // padded with zeros on the left). This isn't really supported by the 871 | // iostreams, but we can approximately simulate it with the width if 872 | // the width isn't otherwise used. 873 | out.width(out.precision() + widthExtra); 874 | out.setf(std::ios::internal, std::ios::adjustfield); 875 | out.fill('0'); 876 | } 877 | return c+1; 878 | } 879 | 880 | 881 | //------------------------------------------------------------------------------ 882 | inline void formatImpl(std::ostream& out, const char* fmt, 883 | const detail::FormatArg* args, 884 | int numArgs) 885 | { 886 | // Saved stream state 887 | std::streamsize origWidth = out.width(); 888 | std::streamsize origPrecision = out.precision(); 889 | std::ios::fmtflags origFlags = out.flags(); 890 | char origFill = out.fill(); 891 | 892 | // "Positional mode" means all format specs should be of the form "%n$..." 893 | // with `n` an integer. We detect this in `streamStateFromFormat`. 894 | bool positionalMode = false; 895 | int argIndex = 0; 896 | while (true) { 897 | fmt = printFormatStringLiteral(out, fmt); 898 | if (*fmt == '\0') { 899 | if (!positionalMode && argIndex < numArgs) { 900 | TINYFORMAT_ERROR("tinyformat: Not enough conversion specifiers in format string"); 901 | } 902 | break; 903 | } 904 | bool spacePadPositive = false; 905 | int ntrunc = -1; 906 | const char* fmtEnd = streamStateFromFormat(out, positionalMode, spacePadPositive, ntrunc, fmt, 907 | args, argIndex, numArgs); 908 | // NB: argIndex may be incremented by reading variable width/precision 909 | // in `streamStateFromFormat`, so do the bounds check here. 910 | if (argIndex >= numArgs) { 911 | TINYFORMAT_ERROR("tinyformat: Too many conversion specifiers in format string"); 912 | return; 913 | } 914 | const FormatArg& arg = args[argIndex]; 915 | // Format the arg into the stream. 916 | if (!spacePadPositive) { 917 | arg.format(out, fmt, fmtEnd, ntrunc); 918 | } 919 | else { 920 | // The following is a special case with no direct correspondence 921 | // between stream formatting and the printf() behaviour. Simulate 922 | // it crudely by formatting into a temporary string stream and 923 | // munging the resulting string. 924 | std::ostringstream tmpStream; 925 | tmpStream.copyfmt(out); 926 | tmpStream.setf(std::ios::showpos); 927 | arg.format(tmpStream, fmt, fmtEnd, ntrunc); 928 | std::string result = tmpStream.str(); // allocates... yuck. 929 | for (size_t i = 0, iend = result.size(); i < iend; ++i) { 930 | if (result[i] == '+') 931 | result[i] = ' '; 932 | } 933 | out << result; 934 | } 935 | if (!positionalMode) 936 | ++argIndex; 937 | fmt = fmtEnd; 938 | } 939 | 940 | // Restore stream state 941 | out.width(origWidth); 942 | out.precision(origPrecision); 943 | out.flags(origFlags); 944 | out.fill(origFill); 945 | } 946 | 947 | } // namespace detail 948 | 949 | 950 | /// List of template arguments format(), held in a type-opaque way. 951 | /// 952 | /// A const reference to FormatList (typedef'd as FormatListRef) may be 953 | /// conveniently used to pass arguments to non-template functions: All type 954 | /// information has been stripped from the arguments, leaving just enough of a 955 | /// common interface to perform formatting as required. 956 | class FormatList 957 | { 958 | public: 959 | FormatList(detail::FormatArg* args, int N) 960 | : m_args(args), m_N(N) { } 961 | 962 | friend void vformat(std::ostream& out, const char* fmt, 963 | const FormatList& list); 964 | 965 | private: 966 | const detail::FormatArg* m_args; 967 | int m_N; 968 | }; 969 | 970 | /// Reference to type-opaque format list for passing to vformat() 971 | typedef const FormatList& FormatListRef; 972 | 973 | 974 | namespace detail { 975 | 976 | // Format list subclass with fixed storage to avoid dynamic allocation 977 | template 978 | class FormatListN : public FormatList 979 | { 980 | public: 981 | #ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES 982 | template 983 | FormatListN(const Args&... args) 984 | : FormatList(&m_formatterStore[0], N), 985 | m_formatterStore { FormatArg(args)... } 986 | { static_assert(sizeof...(args) == N, "Number of args must be N"); } 987 | #else // C++98 version 988 | void init(int) {} 989 | # define TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR(n) \ 990 | \ 991 | template \ 992 | FormatListN(TINYFORMAT_VARARGS(n)) \ 993 | : FormatList(&m_formatterStore[0], n) \ 994 | { TINYFORMAT_ASSERT(n == N); init(0, TINYFORMAT_PASSARGS(n)); } \ 995 | \ 996 | template \ 997 | void init(int i, TINYFORMAT_VARARGS(n)) \ 998 | { \ 999 | m_formatterStore[i] = FormatArg(v1); \ 1000 | init(i+1 TINYFORMAT_PASSARGS_TAIL(n)); \ 1001 | } 1002 | 1003 | TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR) 1004 | # undef TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR 1005 | #endif 1006 | FormatListN(const FormatListN& other) 1007 | : FormatList(&m_formatterStore[0], N) 1008 | { std::copy(&other.m_formatterStore[0], &other.m_formatterStore[N], 1009 | &m_formatterStore[0]); } 1010 | 1011 | private: 1012 | FormatArg m_formatterStore[N]; 1013 | }; 1014 | 1015 | // Special 0-arg version - MSVC says zero-sized C array in struct is nonstandard 1016 | template<> class FormatListN<0> : public FormatList 1017 | { 1018 | public: FormatListN() : FormatList(0, 0) {} 1019 | }; 1020 | 1021 | } // namespace detail 1022 | 1023 | 1024 | //------------------------------------------------------------------------------ 1025 | // Primary API functions 1026 | 1027 | #ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES 1028 | 1029 | /// Make type-agnostic format list from list of template arguments. 1030 | /// 1031 | /// The exact return type of this function is an implementation detail and 1032 | /// shouldn't be relied upon. Instead it should be stored as a FormatListRef: 1033 | /// 1034 | /// FormatListRef formatList = makeFormatList( /*...*/ ); 1035 | template 1036 | detail::FormatListN makeFormatList(const Args&... args) 1037 | { 1038 | return detail::FormatListN(args...); 1039 | } 1040 | 1041 | #else // C++98 version 1042 | 1043 | inline detail::FormatListN<0> makeFormatList() 1044 | { 1045 | return detail::FormatListN<0>(); 1046 | } 1047 | #define TINYFORMAT_MAKE_MAKEFORMATLIST(n) \ 1048 | template \ 1049 | detail::FormatListN makeFormatList(TINYFORMAT_VARARGS(n)) \ 1050 | { \ 1051 | return detail::FormatListN(TINYFORMAT_PASSARGS(n)); \ 1052 | } 1053 | TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_MAKEFORMATLIST) 1054 | #undef TINYFORMAT_MAKE_MAKEFORMATLIST 1055 | 1056 | #endif 1057 | 1058 | /// Format list of arguments to the stream according to the given format string. 1059 | /// 1060 | /// The name vformat() is chosen for the semantic similarity to vprintf(): the 1061 | /// list of format arguments is held in a single function argument. 1062 | inline void vformat(std::ostream& out, const char* fmt, FormatListRef list) 1063 | { 1064 | detail::formatImpl(out, fmt, list.m_args, list.m_N); 1065 | } 1066 | 1067 | 1068 | #ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES 1069 | 1070 | /// Format list of arguments to the stream according to given format string. 1071 | template 1072 | void format(std::ostream& out, const char* fmt, const Args&... args) 1073 | { 1074 | vformat(out, fmt, makeFormatList(args...)); 1075 | } 1076 | 1077 | /// Format list of arguments according to the given format string and return 1078 | /// the result as a string. 1079 | template 1080 | std::string format(const char* fmt, const Args&... args) 1081 | { 1082 | std::ostringstream oss; 1083 | format(oss, fmt, args...); 1084 | return oss.str(); 1085 | } 1086 | 1087 | /// Format list of arguments to std::cout, according to the given format string 1088 | template 1089 | void printf(const char* fmt, const Args&... args) 1090 | { 1091 | format(std::cout, fmt, args...); 1092 | } 1093 | 1094 | template 1095 | void printfln(const char* fmt, const Args&... args) 1096 | { 1097 | format(std::cout, fmt, args...); 1098 | std::cout << '\n'; 1099 | } 1100 | 1101 | 1102 | #else // C++98 version 1103 | 1104 | inline void format(std::ostream& out, const char* fmt) 1105 | { 1106 | vformat(out, fmt, makeFormatList()); 1107 | } 1108 | 1109 | inline std::string format(const char* fmt) 1110 | { 1111 | std::ostringstream oss; 1112 | format(oss, fmt); 1113 | return oss.str(); 1114 | } 1115 | 1116 | inline void printf(const char* fmt) 1117 | { 1118 | format(std::cout, fmt); 1119 | } 1120 | 1121 | inline void printfln(const char* fmt) 1122 | { 1123 | format(std::cout, fmt); 1124 | std::cout << '\n'; 1125 | } 1126 | 1127 | #define TINYFORMAT_MAKE_FORMAT_FUNCS(n) \ 1128 | \ 1129 | template \ 1130 | void format(std::ostream& out, const char* fmt, TINYFORMAT_VARARGS(n)) \ 1131 | { \ 1132 | vformat(out, fmt, makeFormatList(TINYFORMAT_PASSARGS(n))); \ 1133 | } \ 1134 | \ 1135 | template \ 1136 | std::string format(const char* fmt, TINYFORMAT_VARARGS(n)) \ 1137 | { \ 1138 | std::ostringstream oss; \ 1139 | format(oss, fmt, TINYFORMAT_PASSARGS(n)); \ 1140 | return oss.str(); \ 1141 | } \ 1142 | \ 1143 | template \ 1144 | void printf(const char* fmt, TINYFORMAT_VARARGS(n)) \ 1145 | { \ 1146 | format(std::cout, fmt, TINYFORMAT_PASSARGS(n)); \ 1147 | } \ 1148 | \ 1149 | template \ 1150 | void printfln(const char* fmt, TINYFORMAT_VARARGS(n)) \ 1151 | { \ 1152 | format(std::cout, fmt, TINYFORMAT_PASSARGS(n)); \ 1153 | std::cout << '\n'; \ 1154 | } 1155 | 1156 | TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_FORMAT_FUNCS) 1157 | #undef TINYFORMAT_MAKE_FORMAT_FUNCS 1158 | 1159 | #endif 1160 | 1161 | 1162 | } // namespace tinyformat 1163 | 1164 | #endif // TINYFORMAT_H_INCLUDED 1165 | -------------------------------------------------------------------------------- /tinyformat_speed_test.cpp: -------------------------------------------------------------------------------- 1 | #if defined(__linux__) && defined(__clang__) 2 | // Workaround for bug in gcc 4.4 standard library headers when compling with 3 | // clang in C++11 mode. 4 | namespace std { class type_info; } 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include "tinyformat.h" 11 | 12 | void speedTest(const std::string& which) 13 | { 14 | // Following is required so that we're not limited by per-character 15 | // buffering. 16 | std::ios_base::sync_with_stdio(false); 17 | const long maxIter = 2000000L; 18 | if(which == "printf") 19 | { 20 | // libc version 21 | for(long i = 0; i < maxIter; ++i) 22 | printf("%0.10f:%04d:%+g:%s:%p:%c:%%\n", 23 | 1.234, 42, 3.13, "str", (void*)1000, (int)'X'); 24 | } 25 | else if(which == "iostreams") 26 | { 27 | // Std iostreams version. What a mess!! 28 | for(long i = 0; i < maxIter; ++i) 29 | std::cout 30 | << std::setprecision(10) << std::fixed << 1.234 31 | << std::resetiosflags(std::ios::floatfield) << ":" 32 | << std::setw(4) << std::setfill('0') << 42 << std::setfill(' ') << ":" 33 | << std::setiosflags(std::ios::showpos) << 3.13 << std::resetiosflags(std::ios::showpos) << ":" 34 | << "str" << ":" 35 | << (void*)1000 << ":" 36 | << 'X' << ":%\n"; 37 | } 38 | else if(which == "tinyformat") 39 | { 40 | // tinyformat version. 41 | for(long i = 0; i < maxIter; ++i) 42 | tfm::printf("%0.10f:%04d:%+g:%s:%p:%c:%%\n", 43 | 1.234, 42, 3.13, "str", (void*)1000, (int)'X'); 44 | } 45 | else if(which == "boost") 46 | { 47 | // boost::format version 48 | for(long i = 0; i < maxIter; ++i) 49 | std::cout << boost::format("%0.10f:%04d:%+g:%s:%p:%c:%%\n") 50 | % 1.234 % 42 % 3.13 % "str" % (void*)1000 % (int)'X'; 51 | } 52 | else 53 | { 54 | assert(0 && "speed test for which version?"); 55 | } 56 | } 57 | 58 | 59 | int main(int argc, char* argv[]) 60 | { 61 | if(argc >= 2) 62 | speedTest(argv[1]); 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /tinyformat_test.cpp: -------------------------------------------------------------------------------- 1 | #if defined(__linux__) && defined(__clang__) 2 | // Workaround for bug in gcc 4.4 standard library headers when compling with 3 | // clang in C++11 mode. 4 | namespace std { class type_info; } 5 | #endif 6 | 7 | #if defined(_MSC_VER) && _MSC_VER == 1800 8 | // Disable spurious warning about printf format string mismatch in VS 2012 9 | # pragma warning(disable:4313) 10 | #endif 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | // Throw instead of abort() so we can test error conditions. 19 | #define TINYFORMAT_ERROR(reason) \ 20 | throw std::runtime_error(reason); 21 | 22 | #include "tinyformat.h" 23 | #include 24 | 25 | #if 0 26 | // Compare result of tfm::format() to C's sprintf(). 27 | template 28 | void compareSprintf(const Args&... args) 29 | { 30 | std::string tfmResult = tfm::format(args...); 31 | char sprintfResult[200]; 32 | sprintf(sprintfResult, args...); 33 | if (tfmResult != sprintfResult) 34 | { 35 | std::cout << tfmResult << std::endl; 36 | std::cout << sprintfResult << std::endl; 37 | assert(0 && "results didn't match, see above."); 38 | } 39 | } 40 | #endif 41 | 42 | #define EXPECT_ERROR(expression) \ 43 | try \ 44 | { \ 45 | expression; \ 46 | std::cout << "test failed, line " << __LINE__ << "\n"; \ 47 | std::cout << "expected exception in " #expression << "\n"; \ 48 | ++nfailed; \ 49 | } \ 50 | catch (std::runtime_error&) {} \ 51 | 52 | #define CHECK_EQUAL(a, b) \ 53 | if (!((a) == (b))) \ 54 | { \ 55 | std::cout << "test failed, line " << __LINE__ << "\n"; \ 56 | std::cout << (a) << " != " << (b) << "\n"; \ 57 | std::cout << "[" #a ", " #b "]\n"; \ 58 | ++nfailed; \ 59 | } 60 | 61 | 62 | // Test wrapping to create our own function which calls through to tfm::format 63 | struct TestWrap 64 | { 65 | std::ostringstream m_oss; 66 | // template 67 | // std::string error(int code, const char* fmt, const Args&... args); 68 | # define MAKE_ERROR_FUNC(n) \ 69 | template \ 70 | std::string error(int code, const char* fmt, TINYFORMAT_VARARGS(n)) \ 71 | { \ 72 | m_oss.clear(); \ 73 | m_oss << code << ": "; \ 74 | tfm::format(m_oss, fmt, TINYFORMAT_PASSARGS(n)); \ 75 | return m_oss.str(); \ 76 | } 77 | TINYFORMAT_FOREACH_ARGNUM(MAKE_ERROR_FUNC) 78 | }; 79 | 80 | 81 | struct TestExceptionDef : public std::runtime_error 82 | { 83 | # define MAKE_CONSTRUCTOR(n) \ 84 | template \ 85 | TestExceptionDef(const char* fmt, TINYFORMAT_VARARGS(n)) \ 86 | : std::runtime_error(tfm::format(fmt, TINYFORMAT_PASSARGS(n))) \ 87 | { } 88 | TINYFORMAT_FOREACH_ARGNUM(MAKE_CONSTRUCTOR) 89 | }; 90 | 91 | 92 | struct MyInt { 93 | public: 94 | MyInt(int value) : m_value(value) {} 95 | int value() const { return m_value; } 96 | private: 97 | int m_value; 98 | }; 99 | 100 | std::ostream& operator<<(std::ostream& os, const MyInt& obj) { 101 | os << obj.value(); 102 | return os; 103 | } 104 | 105 | 106 | int unitTests() 107 | { 108 | int nfailed = 0; 109 | # if defined(_MSC_VER) && _MSC_VER < 1900 // VC++ older than 2015 110 | // floats are printed with three digit exponents on windows, which messes 111 | // up the tests. Turn this off for consistency: 112 | _set_output_format(_TWO_DIGIT_EXPONENT); 113 | # endif 114 | 115 | //------------------------------------------------------------ 116 | // Test various basic format specs against results of sprintf 117 | CHECK_EQUAL(tfm::format("%s", "asdf"), "asdf"); 118 | CHECK_EQUAL(tfm::format("%d", 1234), "1234"); 119 | CHECK_EQUAL(tfm::format("%i", -5678), "-5678"); 120 | CHECK_EQUAL(tfm::format("%o", 012), "12"); 121 | CHECK_EQUAL(tfm::format("%u", 123456u), "123456"); 122 | CHECK_EQUAL(tfm::format("%x", 0xdeadbeef), "deadbeef"); 123 | CHECK_EQUAL(tfm::format("%X", 0xDEADBEEF), "DEADBEEF"); 124 | CHECK_EQUAL(tfm::format("%e", 1.23456e10), "1.234560e+10"); 125 | CHECK_EQUAL(tfm::format("%E", -1.23456E10), "-1.234560E+10"); 126 | CHECK_EQUAL(tfm::format("%f", -9.8765), "-9.876500"); 127 | CHECK_EQUAL(tfm::format("%F", 9.8765), "9.876500"); 128 | # ifndef _MSC_VER 129 | CHECK_EQUAL(tfm::format("%a", -1.671111047267913818359375), "-0x1.abcdefp+0"); 130 | CHECK_EQUAL(tfm::format("%A", 1.671111047267913818359375), "0X1.ABCDEFP+0"); 131 | # else 132 | CHECK_EQUAL(tfm::format("%a", -1.671111047267913818359375), "-0x1.abcdef0000000p+0"); 133 | CHECK_EQUAL(tfm::format("%A", 1.671111047267913818359375), "0X1.ABCDEF0000000P+0"); 134 | # endif 135 | CHECK_EQUAL(tfm::format("%g", 10), "10"); 136 | CHECK_EQUAL(tfm::format("%G", 100), "100"); 137 | CHECK_EQUAL(tfm::format("%c", 65), "A"); 138 | CHECK_EQUAL(tfm::format("%hc", (short)65), "A"); 139 | CHECK_EQUAL(tfm::format("%lc", (long)65), "A"); 140 | CHECK_EQUAL(tfm::format("%s", "asdf_123098"), "asdf_123098"); 141 | // Test "%%" - (note plain "%%" format gives warning with gcc printf) 142 | CHECK_EQUAL(tfm::format("%%%s", "asdf"), "%asdf"); 143 | // Check that 0-argument formatting is printf-compatible 144 | CHECK_EQUAL(tfm::format("100%%"), "100%"); 145 | 146 | // Test printing of pointers. Note that there's no standard numerical 147 | // representation so this is platform and OS dependent. 148 | // 149 | // Also test special case when %p is used with `char*` pointer types. In 150 | // this case the implementation needs to take care to cast to void* 151 | // before invoking `operator<<`; it must not dereference the pointer and 152 | // print as a string. 153 | # ifdef _MSC_VER 154 | # ifdef _WIN64 155 | CHECK_EQUAL(tfm::format("%p", (void*)0x12345), "0000000000012345"); 156 | CHECK_EQUAL(tfm::format("%p", (const char*)0x10), "0000000000000010"); 157 | # else 158 | CHECK_EQUAL(tfm::format("%p", (void*)0x12345), "00012345"); 159 | CHECK_EQUAL(tfm::format("%p", (const char*)0x10), "00000010"); 160 | # endif // _WIN64 161 | # else 162 | CHECK_EQUAL(tfm::format("%p", (void*)0x12345), "0x12345"); 163 | CHECK_EQUAL(tfm::format("%p", (const char*)0x10), "0x10"); 164 | CHECK_EQUAL(tfm::format("%p", (char*)0x10), "0x10"); 165 | CHECK_EQUAL(tfm::format("%p", (signed char*)0x10), "0x10"); 166 | CHECK_EQUAL(tfm::format("%p", (unsigned char*)0x10), "0x10"); 167 | # endif // !_MSC_VER 168 | 169 | // chars with int format specs are printed as ints: 170 | CHECK_EQUAL(tfm::format("%hhd", (char)65), "65"); 171 | CHECK_EQUAL(tfm::format("%hhu", (unsigned char)65), "65"); 172 | CHECK_EQUAL(tfm::format("%hhd", (signed char)65), "65"); 173 | // Bools with string format spec are printed as "true" or "false". 174 | CHECK_EQUAL(tfm::format("%s", true), "true"); 175 | CHECK_EQUAL(tfm::format("%d", true), "1"); 176 | 177 | //------------------------------------------------------------ 178 | // Simple tests of posix positional arguments 179 | CHECK_EQUAL(tfm::format("%2$d %1$d", 10, 20), "20 10"); 180 | // Allow positional arguments to be unreferenced. This is a slight 181 | // generalization of posix printf, which appears to allow only trailing 182 | // arguments to be unreferenced. See 183 | // http://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html 184 | CHECK_EQUAL(tfm::format("%1$d", 10, 20), "10"); 185 | CHECK_EQUAL(tfm::format("%2$d", 10, 20), "20"); 186 | 187 | //------------------------------------------------------------ 188 | // Test precision & width 189 | CHECK_EQUAL(tfm::format("%10d", -10), " -10"); 190 | CHECK_EQUAL(tfm::format("%.4d", 10), "0010"); 191 | CHECK_EQUAL(tfm::format("%10.4f", 1234.1234567890), " 1234.1235"); 192 | CHECK_EQUAL(tfm::format("%.f", 10.1), "10"); 193 | // Per C++ spec, iostreams ignore the precision for "%a" to avoid precision 194 | // loss. This is a printf incompatibility. 195 | # ifndef _MSC_VER 196 | CHECK_EQUAL(tfm::format("%.1a", 1.13671875), "0x1.23p+0"); 197 | CHECK_EQUAL(tfm::format("%14a", 1.671111047267913818359375), " 0x1.abcdefp+0"); 198 | # else 199 | // MSVC workaround 200 | CHECK_EQUAL(tfm::format("%.1a", 1.13671875), "0x1.2300000000000p+0"); 201 | CHECK_EQUAL(tfm::format("%21a", 1.671111047267913818359375), " 0x1.abcdef0000000p+0"); 202 | # endif 203 | CHECK_EQUAL(tfm::format("%.2s", "asdf"), "as"); // strings truncate to precision 204 | CHECK_EQUAL(tfm::format("%.2s", std::string("asdf")), "as"); 205 | // Test variable precision & width 206 | CHECK_EQUAL(tfm::format("%*.4f", 10, 1234.1234567890), " 1234.1235"); 207 | CHECK_EQUAL(tfm::format("%10.*f", 4, 1234.1234567890), " 1234.1235"); 208 | CHECK_EQUAL(tfm::format("%*.*f", 10, 4, 1234.1234567890), " 1234.1235"); 209 | CHECK_EQUAL(tfm::format("%*.*f", -10, 4, 1234.1234567890), "1234.1235 "); 210 | CHECK_EQUAL(tfm::format("%.*f", -4, 1234.1234567890), "1234.123457"); // negative precision ignored 211 | // Test variable precision & width with positional arguments 212 | CHECK_EQUAL(tfm::format("%1$*2$.4f", 1234.1234567890, 10), " 1234.1235"); 213 | CHECK_EQUAL(tfm::format("%1$10.*2$f", 1234.1234567890, 4), " 1234.1235"); 214 | CHECK_EQUAL(tfm::format("%1$*3$.*2$f", 1234.1234567890, 4, 10), " 1234.1235"); 215 | CHECK_EQUAL(tfm::format("%1$*2$.*3$f", 1234.1234567890, -10, 4), "1234.1235 "); 216 | // Test padding for infinity and NaN 217 | // (Visual Studio 12.0 and earlier print these in a different way) 218 | # if !defined(_MSC_VER) || _MSC_VER >= 1900 219 | CHECK_EQUAL(tfm::format("%.3d", std::numeric_limits::infinity()), "inf"); 220 | CHECK_EQUAL(tfm::format("%.4d", std::numeric_limits::infinity()), " inf"); 221 | CHECK_EQUAL(tfm::format("%04.0f", std::numeric_limits::infinity()), " inf"); 222 | CHECK_EQUAL(tfm::format("%.3d", std::numeric_limits::quiet_NaN()), "nan"); 223 | CHECK_EQUAL(tfm::format("%.4d", std::numeric_limits::quiet_NaN()), " nan"); 224 | CHECK_EQUAL(tfm::format("%04.0f", std::numeric_limits::quiet_NaN()), " nan"); 225 | # endif 226 | 227 | //------------------------------------------------------------ 228 | // Test flags 229 | CHECK_EQUAL(tfm::format("%#x", 0x271828), "0x271828"); 230 | CHECK_EQUAL(tfm::format("%#o", 0x271828), "011614050"); 231 | CHECK_EQUAL(tfm::format("%#f", 3.0), "3.000000"); 232 | CHECK_EQUAL(tfm::format("%+d", 3), "+3"); 233 | CHECK_EQUAL(tfm::format("%+d", 0), "+0"); 234 | CHECK_EQUAL(tfm::format("%+d", -3), "-3"); 235 | CHECK_EQUAL(tfm::format("%010d", 100), "0000000100"); 236 | CHECK_EQUAL(tfm::format("%010d", -10), "-000000010"); // sign should extend 237 | CHECK_EQUAL(tfm::format("%#010X", 0xBEEF), "0X0000BEEF"); 238 | CHECK_EQUAL(tfm::format("% d", 10), " 10"); 239 | CHECK_EQUAL(tfm::format("% d", -10), "-10"); 240 | // Test flags with variable precision & width 241 | CHECK_EQUAL(tfm::format("%+.2d", 3), "+03"); 242 | CHECK_EQUAL(tfm::format("%+.2d", -3), "-03"); 243 | // flag override precedence 244 | CHECK_EQUAL(tfm::format("%+ d", 10), "+10"); // '+' overrides ' ' 245 | CHECK_EQUAL(tfm::format("% +d", 10), "+10"); 246 | CHECK_EQUAL(tfm::format("%-010d", 10), "10 "); // '-' overrides '0' 247 | CHECK_EQUAL(tfm::format("%0-10d", 10), "10 "); 248 | 249 | //------------------------------------------------------------ 250 | // Check that length modifiers are ignored 251 | CHECK_EQUAL(tfm::format("%hd", (short)1000), "1000"); 252 | CHECK_EQUAL(tfm::format("%ld", (long)100000), "100000"); 253 | CHECK_EQUAL(tfm::format("%lld", (long long)100000), "100000"); 254 | CHECK_EQUAL(tfm::format("%zd", (size_t)100000), "100000"); 255 | CHECK_EQUAL(tfm::format("%td", (ptrdiff_t)100000), "100000"); 256 | CHECK_EQUAL(tfm::format("%jd", 100000), "100000"); 257 | 258 | // printf incompatibilities: 259 | // compareSprintf("%6.4x", 10); // precision & width can't be supported independently 260 | // compareSprintf("%.4d", -10); // negative numbers + precision don't quite work. 261 | 262 | //------------------------------------------------------------ 263 | // General "complicated" format spec test 264 | CHECK_EQUAL(tfm::format("%0.10f:%04d:%+g:%s:%#X:%c:%%:%%asdf", 265 | 1.234, 42, 3.13, "str", 0XDEAD, (int)'X'), 266 | "1.2340000000:0042:+3.13:str:0XDEAD:X:%:%asdf"); 267 | 268 | CHECK_EQUAL(tfm::format("%2$0.10f:%3$0*4$d:%1$+g:%6$s:%5$#X:%7$c:%%:%%asdf", 269 | 3.13, 1.234, 42, 4, 0XDEAD, "str", (int)'X'), 270 | "1.2340000000:0042:+3.13:str:0XDEAD:X:%:%asdf"); 271 | 272 | //------------------------------------------------------------ 273 | // Error handling 274 | // Test wrong number of args 275 | EXPECT_ERROR( tfm::format("%d", 5, 10) ) 276 | EXPECT_ERROR( tfm::format("%d %d", 1) ) 277 | // Unterminated format spec 278 | EXPECT_ERROR( tfm::format("%123", 10) ) 279 | // Types used to specify variable width/precision must be convertible to int. 280 | EXPECT_ERROR( tfm::format("%0*d", "thing that can't convert to int", 42) ) 281 | EXPECT_ERROR( tfm::format("%0.*d", "thing that can't convert to int", 42) ) 282 | // Error required if not enough args for variable width/precision 283 | EXPECT_ERROR( tfm::format("%*d", 1) ) 284 | EXPECT_ERROR( tfm::format("%.*d", 1) ) 285 | EXPECT_ERROR( tfm::format("%*.*d", 1, 2) ) 286 | // Error required if positional argument refers to non-existent argument 287 | EXPECT_ERROR( tfm::format("%2$d", 1) ) 288 | EXPECT_ERROR( tfm::format("%0$d", 1) ) 289 | EXPECT_ERROR( tfm::format("%1$.*3$d", 1, 2) ) 290 | EXPECT_ERROR( tfm::format("%1$.*0$d", 1, 2) ) 291 | EXPECT_ERROR( tfm::format("%1$.*$d", 1, 2) ) 292 | EXPECT_ERROR( tfm::format("%3$*4$.*2$d", 1, 2, 3) ) 293 | EXPECT_ERROR( tfm::format("%3$*0$.*2$d", 1, 2, 3) ) 294 | 295 | // Unhandled C99 format spec 296 | EXPECT_ERROR( tfm::format("%n", 10) ) 297 | 298 | //------------------------------------------------------------ 299 | // Misc 300 | volatile int i = 1234; 301 | CHECK_EQUAL(tfm::format("%d", i), "1234"); 302 | 303 | #ifdef TEST_WCHAR_T_COMPILE 304 | // Test wchar_t handling - should fail to compile! 305 | tfm::format("%ls", L"blah"); 306 | #endif 307 | 308 | // Test that formatting is independent of underlying stream state. 309 | std::ostringstream oss; 310 | oss.width(20); 311 | oss.precision(10); 312 | oss.fill('*'); 313 | oss.setf(std::ios::scientific); 314 | tfm::format(oss, "%f", 10.1234123412341234); 315 | CHECK_EQUAL(oss.str(), "10.123412"); 316 | 317 | // Test formatting a custom object 318 | MyInt myobj(42); 319 | CHECK_EQUAL(tfm::format("myobj: %s", myobj), "myobj: 42"); 320 | 321 | // Test that interface wrapping works correctly 322 | TestWrap wrap; 323 | CHECK_EQUAL(wrap.error(10, "someformat %s:%d:%d", "asdf", 2, 4), 324 | "10: someformat asdf:2:4"); 325 | 326 | TestExceptionDef ex("blah %d", 100); 327 | CHECK_EQUAL(ex.what(), std::string("blah 100")); 328 | 329 | // Test tfm::printf by swapping the std::cout stream buffer to capture data 330 | // which would noramlly go to the stdout 331 | std::ostringstream coutCapture; 332 | std::streambuf* coutBuf = std::cout.rdbuf(coutCapture.rdbuf()); 333 | tfm::printf("%s %s %d\n", "printf", "test", 1); 334 | tfm::printfln("%s %s %d", "printfln", "test", 1); 335 | std::cout.rdbuf(coutBuf); // restore buffer 336 | CHECK_EQUAL(coutCapture.str(), "printf test 1\nprintfln test 1\n"); 337 | 338 | return nfailed; 339 | } 340 | 341 | 342 | int main() 343 | { 344 | try 345 | { 346 | return unitTests(); 347 | } 348 | catch (std::runtime_error & e) 349 | { 350 | std::cout << "Failure due to uncaught exception: " << e.what() << std::endl; 351 | return EXIT_FAILURE; 352 | } 353 | } 354 | --------------------------------------------------------------------------------