{"id":13418842,"url":"https://github.com/c42f/tinyformat","last_synced_at":"2025-05-16T03:05:21.858Z","repository":{"id":46886044,"uuid":"2111398","full_name":"c42f/tinyformat","owner":"c42f","description":"Minimal, type safe printf replacement library for C++","archived":false,"fork":false,"pushed_at":"2024-01-31T01:04:58.000Z","size":314,"stargazers_count":545,"open_issues_count":19,"forks_count":75,"subscribers_count":32,"default_branch":"master","last_synced_at":"2025-05-10T02:43:31.471Z","etag":null,"topics":["cplusplus","minimalistic","printf"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/c42f.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2011-07-27T06:40:32.000Z","updated_at":"2025-04-26T03:37:47.000Z","dependencies_parsed_at":"2024-10-25T18:35:07.339Z","dependency_job_id":"ee2e853a-00dc-484f-bda7-850d43297b3a","html_url":"https://github.com/c42f/tinyformat","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c42f%2Ftinyformat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c42f%2Ftinyformat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c42f%2Ftinyformat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c42f%2Ftinyformat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/c42f","download_url":"https://codeload.github.com/c42f/tinyformat/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253908663,"owners_count":21982682,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cplusplus","minimalistic","printf"],"created_at":"2024-07-30T22:01:07.780Z","updated_at":"2025-05-16T03:05:16.846Z","avatar_url":"https://github.com/c42f.png","language":"C++","readme":"# tinyformat.h\n\n## A minimal type safe printf() replacement\n\n**tinyformat.h** is a type safe printf replacement library in a single C++\nheader file.  If you've ever wanted `printf(\"%s\", s)` to just work regardless\nof the type of `s`, tinyformat might be for you.  Design goals include:\n\n* Type safety and extensibility for user defined types.\n* C99 `printf()` compatibility, to the extent possible using `std::ostream`\n* POSIX extension for positional arguments\n* Simplicity and minimalism.  A single header file to include and distribute\n  with your projects.\n* Augment rather than replace the standard stream formatting mechanism\n* C++98 support, with optional C++11 niceties\n\nBuild status, master branch:\n[![Linux/OSX build](https://travis-ci.org/c42f/tinyformat.svg?branch=master)](https://travis-ci.org/c42f/tinyformat)\n[![Windows build](https://ci.appveyor.com/api/projects/status/rwxqhhy6v5m0p1aq/branch/master?svg=true)](https://ci.appveyor.com/project/c42f/tinyformat/branch/master)\n\n## Quickstart\n\nTo print a date to `std::cout`:\n\n```C++\nstd::string weekday = \"Wednesday\";\nconst char* month = \"July\";\nsize_t day = 27;\nlong hour = 14;\nint min = 44;\n\ntfm::printf(\"%s, %s %d, %.2d:%.2d\\n\", weekday, month, day, hour, min);\n```\n\nPOSIX extension for positional arguments is available.\nThe ability to rearrange formatting arguments is an important feature\nfor localization because the word order may vary in different languages.\n\nPrevious example for German usage. Arguments are reordered:\n\n```C++\ntfm::printf(\"%1$s, %3$d. %2$s, %4$d:%5$.2d\\n\", weekday, month, day, hour, min);\n```\n\nThe strange types here emphasize the type safety of the interface, for example\nit is possible to print a `std::string` using the `\"%s\"` conversion, and a\n`size_t` using the `\"%d\"` conversion.  A similar result could be achieved\nusing either of the `tfm::format()` functions.  One prints on a user provided\nstream:\n\n```C++\ntfm::format(std::cerr, \"%s, %s %d, %.2d:%.2d\\n\",\n            weekday, month, day, hour, min);\n```\n\nThe other returns a `std::string`:\n\n```C++\nstd::string date = tfm::format(\"%s, %s %d, %.2d:%.2d\\n\",\n                               weekday, month, day, hour, min);\nstd::cout \u003c\u003c date;\n```\n\n\nIt is safe to use tinyformat inside a template function.  For any type which\nhas the usual stream insertion `operator\u003c\u003c` defined, the following will work\nas desired:\n\n```C++\ntemplate\u003ctypename T\u003e\nvoid myPrint(const T\u0026 value)\n{\n    tfm::printf(\"My value is '%s'\\n\", value);\n}\n```\n\n(The above is a compile error for types `T` without a stream insertion\noperator.)\n\n\n## Function reference\n\nAll user facing functions are defined in the namespace `tinyformat`.  A\nnamespace alias `tfm` is provided to encourage brevity, but can easily be\ndisabled if desired.\n\nThree main interface functions are available: an iostreams-based `format()`,\na string-based `format()` and a `printf()` replacement.  These functions\ncan be thought of as C++ replacements for C's `fprintf()`, `sprintf()` and\n`printf()` functions respectively.  All the interface functions can take an\nunlimited number of input arguments if compiled with C++11 variadic templates\nsupport.  In C++98 mode, the number of arguments must be limited to some fixed\nupper bound which is currently 16 as of version 1.3. Supporting more arguments\nis quite easy using the in-source code generator based on\n[cog.py](http://nedbatchelder.com/code/cog) - see the source for details.\n\nThe `format()` function which takes a stream as the first argument is the\nmain part of the tinyformat interface.  `stream` is the output stream,\n`formatString` is a format string in C99 `printf()` format, and the values\nto be formatted have arbitrary types:\n\n```C++\ntemplate\u003ctypename... Args\u003e\nvoid format(std::ostream\u0026 stream, const char* formatString,\n            const Args\u0026... args);\n```\n\nThe second version of `format()` is a convenience function which returns a\n`std::string` rather than printing onto a stream.  This function simply\ncalls the main version of `format()` using a `std::ostringstream`, and\nreturns the resulting string:\n\n```C++\ntemplate\u003ctypename... Args\u003e\nstd::string format(const char* formatString, const Args\u0026... args);\n```\n\nFinally, `printf()` and `printfln()` are convenience functions which call\n`format()` with `std::cout` as the first argument; both have the same\nsignature:\n\n```C++\ntemplate\u003ctypename... Args\u003e\nvoid printf(const char* formatString, const Args\u0026... args);\n```\n\n`printfln()` is the same as `printf()` but appends an additional newline\nfor convenience - a concession to the author's tendency to forget the newline\nwhen using the library for simple logging.\n\n## Format strings and type safety\n\nTinyformat parses C99 format strings to guide the formatting process --- please\nrefer to any standard C99 printf documentation for format string syntax.  In\ncontrast to printf, tinyformat does not use the format string to decide on\nthe type to be formatted so this does not compromise the type safety: *you may\nuse any format specifier with any C++ type*.  The author suggests standardising\non the `%s` conversion unless formatting numeric types.\n\nLet's look at what happens when you execute the function call:\n\n```C++\ntfm::format(outStream, \"%+6.4f\", yourType);\n```\n\nFirst, the library parses the format string, and uses it to modify the state of\n`outStream`:\n\n1. The `outStream` formatting flags are cleared and the width, precision and\n   fill reset to the default.\n2. The flag `'+'` means to prefix positive numbers with a `'+'`; tinyformat\n   executes `outStream.setf(std::ios::showpos)`\n3. The number 6 gives the field width; execute `outStream.width(6)`.\n4. The number 4 gives the precision; execute `outStream.precision(4)`.\n5. The conversion specification character `'f'` means that floats should be\n   formatted with a fixed number of digits; this corresponds to executing\n   `outStream.setf(std::ios::fixed, std::ios::floatfield);`\n\nAfter all these steps, tinyformat executes:\n\n```C++\noutStream \u003c\u003c yourType;\n```\n\nand finally restores the stream flags, precision and fill.\n\nWhat happens if `yourType` isn't actually a floating point type?  In this\ncase the flags set above are probably irrelevant and will be ignored by the\nunderlying `std::ostream` implementation.  The field width of six may cause\nsome padding in the output of `yourType`, but that's about it.\n\n\n### Special cases for \"%p\", \"%c\" and \"%s\"\n\nTinyformat normally uses `operator\u003c\u003c` to convert types to strings.  However,\nthe \"%p\" and \"%c\" conversions require special rules for robustness.  Consider:\n\n```C++\nuint8_t* pixels = get_pixels(/* ... */);\ntfm::printf(\"%p\", pixels);\n```\n\nClearly the intention here is to print a representation of the *pointer* to\n`pixels`, but since `uint8_t` is a character type the compiler would\nattempt to print it as a C string if we blindly fed it into `operator\u003c\u003c`.  To\ncounter this kind of madness, tinyformat tries to static_cast any type fed to\nthe \"%p\" conversion into a `const void*` before printing.  If this can't be\ndone at compile time the library falls back to using `operator\u003c\u003c` as usual.\n\nThe \"%c\" conversion has a similar problem: it signifies that the given integral\ntype should be converted into a `char` before printing.  The solution is\nidentical: attempt to convert the provided type into a char using\n`static_cast` if possible, and if not fall back to using `operator\u003c\u003c`.\n\nThe \"%s\" conversion sets the boolalpha flag on the formatting stream.  This\nmeans that a `bool` variable printed with \"%s\" will come out as `true` or\n`false` rather than the `1` or `0` that you would otherwise get.\n\n\n### Incompatibilities with C99 printf\n\nNot all features of printf can be simulated simply using standard iostreams.\nHere's a list of known incompatibilities:\n\n* The `\"%a\"` and `\"%A\"` hexadecimal floating point conversions ignore precision\n  as stream output of hexfloat (introduced in C++11) ignores precision, always\n  outputting the minimum number of digits required for exact representation.\n  MSVC incorrectly honors stream precision, so we force precision to 13 in this\n  case to guarentee lossless roundtrip conversion.\n* The precision for integer conversions cannot be supported by the iostreams\n  state independently of the field width.  (Note: **this is only a\n  problem for certain obscure integer conversions**; float conversions like\n  `%6.4f` work correctly.)  In tinyformat the field width takes precedence,\n  so the 4 in `%6.4d` will be ignored.  However, if the field width is not\n  specified, the width used internally is set equal to the precision and padded\n  with zeros on the left.  That is, a conversion like `%.4d` effectively\n  becomes `%04d` internally.  This isn't correct for every case (eg, negative\n  numbers end up with one less digit than desired) but it's about the closest\n  simple solution within the iostream model.\n* The `\"%n\"` query specifier isn't supported to keep things simple and will\n  result in a call to `TINYFORMAT_ERROR`.\n* The `\"%ls\"` conversion is not supported, and attempting to format a\n  `wchar_t` array will cause a compile time error to minimise unexpected\n  surprises.  If you know the encoding of your wchar_t strings, you could write\n  your own `std::ostream` insertion operator for them, and disable the\n  compile time check by defining the macro `TINYFORMAT_ALLOW_WCHAR_STRINGS`.\n  If you want to print the *address* of a wide character with the `\"%p\"`\n  conversion, you should cast it to a `void*` before passing it to one of the\n  formatting functions.\n\n\n## Error handling\n\nBy default, tinyformat calls `assert()` if it encounters an error in the\nformat string or number of arguments.  This behaviour can be changed (for\nexample, to throw an exception) by defining the `TINYFORMAT_ERROR` macro\nbefore including tinyformat.h, or editing the config section of the header.\n\n\n## Formatting user defined types\n\nUser defined types with a stream insertion operator will be formatted using\n`operator\u003c\u003c(std::ostream\u0026, T)` by default.  The `\"%s\"` format specifier is\nsuggested for user defined types, unless the type is inherently numeric.\n\nFor further customization, the user can override the `formatValue()`\nfunction to specify formatting independently of the stream insertion operator.\nIf you override this function, the library will have already parsed the format\nspecification and set the stream flags accordingly - see the source for details.\n\n\n## Wrapping tfm::format() inside a user defined format function\n\nSuppose you wanted to define your own function which wraps `tfm::format`.\nFor example, consider an error function taking an error code, which in C++11\nmight be written simply as:\n\n```C++\ntemplate\u003ctypename... Args\u003e\nvoid error(int code, const char* fmt, const Args\u0026... args)\n{\n    std::cerr \u003c\u003c \"error (code \" \u003c\u003c code \u003c\u003c \")\";\n    tfm::format(std::cerr, fmt, args...);\n}\n```\n\nSimulating this functionality in C++98 is pretty painful since it requires\nwriting out a version of `error()` for each desired number of arguments.  To\nmake this bearable tinyformat comes with a set of macros which are used\ninternally to generate the API, but which may also be used in user code.\n\nThe three macros `TINYFORMAT_ARGTYPES(n)`, `TINYFORMAT_VARARGS(n)` and\n`TINYFORMAT_PASSARGS(n)` will generate a list of `n` argument types,\ntype/name pairs and argument names respectively when called with an integer\n`n` between 1 and 16.  We can use these to define a macro which generates the\ndesired user defined function with `n` arguments.  This should be followed by\na call to `TINYFORMAT_FOREACH_ARGNUM` to generate the set of functions for\nall supported `n`:\n\n```C++\n#define MAKE_ERROR_FUNC(n)                                    \\\ntemplate\u003cTINYFORMAT_ARGTYPES(n)\u003e                              \\\nvoid error(int code, const char* fmt, TINYFORMAT_VARARGS(n))  \\\n{                                                             \\\n    std::cerr \u003c\u003c \"error (code \" \u003c\u003c code \u003c\u003c \")\";               \\\n    tfm::format(std::cerr, fmt, TINYFORMAT_PASSARGS(n));      \\\n}\nTINYFORMAT_FOREACH_ARGNUM(MAKE_ERROR_FUNC)\n```\n\nSometimes it's useful to be able to pass a list of format arguments through to\na non-template function.  The `FormatList` class is provided as a way to do\nthis by storing the argument list in a type-opaque way.  For example:\n\n```C++\ntemplate\u003ctypename... Args\u003e\nvoid error(int code, const char* fmt, const Args\u0026... args)\n{\n    tfm::FormatListRef formatList = tfm::makeFormatList(args...);\n    errorImpl(code, fmt, formatList);\n}\n```\n\nWhat's interesting here is that `errorImpl()` is a non-template function so\nit could be separately compiled if desired.  The `FormatList` instance can be\nused via a call to the `vformat()` function (the name chosen for semantic\nsimilarity to `vprintf()`):\n\n```C++\nvoid errorImpl(int code, const char* fmt, tfm::FormatListRef formatList)\n{\n    std::cerr \u003c\u003c \"error (code \" \u003c\u003c code \u003c\u003c \")\";\n    tfm::vformat(std::cout, fmt, formatList);\n}\n```\n\nThe construction of a `FormatList` instance is very lightweight - it defers\nall formatting and simply stores a couple of function pointers and a value\npointer per argument.  Since most of the actual work is done inside\n`vformat()`, any logic which causes an early exit of `errorImpl()` -\nfiltering of verbose log messages based on error code for example - could be a\nuseful optimization for programs using tinyformat.  (A faster option would be\nto write any early bailout code inside `error()`, though this must be done in\nthe header.)\n\n\n## Benchmarks\n\n### Compile time and code bloat\n\nThe script `bloat_test.sh` included in the repository tests whether\ntinyformat succeeds in avoiding compile time and code bloat for nontrivial\nprojects.  The idea is to include `tinyformat.h` into 100 translation units\nand use `printf()` five times in each to simulate a medium sized project.\nThe resulting executable size and compile time (g++-4.8.2, linux ubuntu 14.04)\nis shown in the following tables, which can be regenerated using `make\nbloat_test`:\n\n**Non-optimized build**\n\n| test name              | compiler wall time | executable size (stripped) |\n| ---------------------- | ------------------ | -------------------------- |\n| libc printf            | 1.8s               | 48K  (36K)                 |\n| std::ostream           | 10.7s              | 96K  (76K)                 |\n| tinyformat, no inlines | 18.9s              | 140K (104K)                |\n| tinyformat             | 21.1s              | 220K (180K)                |\n| tinyformat, c++0x mode | 20.7s              | 220K (176K)                |\n| boost::format          | 70.1s              | 844K (736K)                |\n\n**Optimized build (-O3 -DNDEBUG)**\n\n| test name              | compiler wall time | executable size (stripped) |\n| ---------------------- | ------------------ | -------------------------- |\n| libc printf            | 2.3s               | 40K  (28K)                 |\n| std::ostream           | 11.8s              | 104K (80K)                 |\n| tinyformat, no inlines | 23.0s              | 128K (104K)                |\n| tinyformat             | 32.9s              | 128K (104K)                |\n| tinyformat, c++0x mode | 34.0s              | 128K (104K)                |\n| boost::format          | 147.9s             | 644K (600K)                |\n\nFor large projects it's arguably worthwhile to do separate compilation of the\nnon-templated parts of tinyformat, as shown in the rows labelled *tinyformat,\nno inlines*.  These were generated by putting the implementation of `vformat`\n(`detail::formatImpl()` etc) it into a separate file, tinyformat.cpp.  Note\nthat the results above can vary considerably with different compilers.  For\nexample, the `-fipa-cp-clone` optimization pass in g++-4.6 resulted in\nexcessively large binaries.  On the other hand, the g++-4.8 results are quite\nsimilar to using clang++-3.4.\n\n\n### Speed tests\n\nThe following speed tests results were generated by building\n`tinyformat_speed_test.cpp` on an Intel core i7-2600K running Linux Ubuntu\n14.04 with g++-4.8.2 using `-O3 -DNDEBUG`.  In the test, the format string\n`\"%0.10f:%04d:%+g:%s:%p:%c:%%\\n\"` is filled 2000000 times with output sent to\n`/dev/null`; for further details see the source and Makefile.\n\n| test name      | run time |\n| -------------- | -------- |\n| libc printf    | 1.20s    |\n| std::ostream   | 1.82s    |\n| tinyformat     | 2.08s    |\n| boost::format  | 9.04s    |\n\nIt's likely that tinyformat has an advantage over boost.format because it tries\nreasonably hard to avoid formatting into temporary strings, preferring instead\nto send the results directly to the stream buffer.  Tinyformat cannot\nbe faster than the iostreams because it uses them internally, but it comes\nacceptably close.\n\n\n## Rationale\n\nOr, why did I reinvent this particularly well studied wheel?\n\nNearly every program needs text formatting in some form but in many cases such\nformatting is *incidental* to the main purpose of the program.  In these cases,\nyou really want a library which is simple to use but as lightweight as\npossible.\n\nThe ultimate in lightweight dependencies are the solutions provided by the C++\nand C libraries.  However, both the C++ iostreams and C's printf() have\nwell known usability problems: iostreams are hopelessly verbose for complicated\nformatting and printf() lacks extensibility and type safety.  For example:\n\n```C++\n// Verbose; hard to read, hard to type:\nstd::cout \u003c\u003c std::setprecision(2) \u003c\u003c std::fixed \u003c\u003c 1.23456 \u003c\u003c \"\\n\";\n// The alternative using a format string is much easier on the eyes\ntfm::printf(\"%.2f\\n\", 1.23456);\n\n// Type mismatch between \"%s\" and int: will cause a segfault at runtime!\nprintf(\"%s\", 1);\n// The following is perfectly fine, and will result in \"1\" being printed.\ntfm::printf(\"%s\", 1);\n```\n\nOn the other hand, there are plenty of excellent and complete libraries which\nsolve the formatting problem in great generality (boost.format and fastformat\ncome to mind, but there are many others).  Unfortunately these kind of\nlibraries tend to be rather heavy dependencies, far too heavy for projects\nwhich need to do only a little formatting.  Problems include\n\n1. Having many large source files.  This makes a heavy dependency unsuitable to\n   bundle within other projects for convenience.\n2. Slow build times for every file using any sort of formatting (this is very\n   noticeable with g++ and boost/format.hpp. I'm not sure about the various\n   other alternatives.)\n3. Code bloat due to instantiating many templates\n\nTinyformat tries to solve these problems while providing formatting which is\nsufficiently general and fast for incidental day to day uses.\n\n\n## License\n\nFor minimum license-related fuss, tinyformat.h is distributed under the boost\nsoftware license, version 1.0.  (Summary: you must keep the license text on\nall source copies, but don't have to mention tinyformat when distributing\nbinaries.)\n\n\n## Author and acknowledgements\n\nTinyformat is written and maintained by Chris Foster, with various contributions\ngratefully recieved [from the community](https://github.com/c42f/tinyformat/graphs/contributors).\n\nOriginally the implementation was inspired by the way `boost::format` uses\nstream based formatting to simulate most of the `printf()` syntax, and Douglas\nGregor'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).\n\n## Bugs\n\nHere's a list of known bugs which are probably cumbersome to fix:\n\n* Field padding won't work correctly with complicated user defined types.  For\n  general types, the only way to do this correctly seems to be format to a\n  temporary string stream, check the length, and finally send to the output\n  stream with padding if necessary.  Doing this for all types would be\n  quite inelegant because it implies extra allocations to make the temporary\n  stream.  A workaround is to add logic to `operator\u003c\u003c()` for composite user\n  defined types so they are aware of the stream field width.\n","funding_links":[],"categories":["TODO scan for Android support in followings","C++","Libraries"],"sub_categories":["String formatting \u0026 templating"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc42f%2Ftinyformat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fc42f%2Ftinyformat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc42f%2Ftinyformat/lists"}