SkillAgentSearch skills...

Tinyformat

Minimal, type safe printf replacement library for C++

Install / Use

/learn @c42f/Tinyformat
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

tinyformat.h

A minimal type safe printf() replacement

tinyformat.h is a type safe printf replacement library in a single C++ header file. If you've ever wanted printf("%s", s) to just work regardless of the type of s, tinyformat might be for you. Design goals include:

  • Type safety and extensibility for user defined types.
  • C99 printf() compatibility, to the extent possible using std::ostream
  • POSIX extension for positional arguments
  • Simplicity and minimalism. A single header file to include and distribute with your projects.
  • Augment rather than replace the standard stream formatting mechanism
  • C++98 support, with optional C++11 niceties

Build status, master branch: Linux/OSX build Windows build

Quickstart

To print a date to std::cout:

std::string weekday = "Wednesday";
const char* month = "July";
size_t day = 27;
long hour = 14;
int min = 44;

tfm::printf("%s, %s %d, %.2d:%.2d\n", weekday, month, day, hour, min);

POSIX extension for positional arguments is available. The ability to rearrange formatting arguments is an important feature for localization because the word order may vary in different languages.

Previous example for German usage. Arguments are reordered:

tfm::printf("%1$s, %3$d. %2$s, %4$d:%5$.2d\n", weekday, month, day, hour, min);

The strange types here emphasize the type safety of the interface, for example it is possible to print a std::string using the "%s" conversion, and a size_t using the "%d" conversion. A similar result could be achieved using either of the tfm::format() functions. One prints on a user provided stream:

tfm::format(std::cerr, "%s, %s %d, %.2d:%.2d\n",
            weekday, month, day, hour, min);

The other returns a std::string:

std::string date = tfm::format("%s, %s %d, %.2d:%.2d\n",
                               weekday, month, day, hour, min);
std::cout << date;

It is safe to use tinyformat inside a template function. For any type which has the usual stream insertion operator<< defined, the following will work as desired:

template<typename T>
void myPrint(const T& value)
{
    tfm::printf("My value is '%s'\n", value);
}

(The above is a compile error for types T without a stream insertion operator.)

Function reference

All user facing functions are defined in the namespace tinyformat. A namespace alias tfm is provided to encourage brevity, but can easily be disabled if desired.

Three main interface functions are available: an iostreams-based format(), a string-based format() and a printf() replacement. These functions can be thought of as C++ replacements for C's fprintf(), sprintf() and printf() functions respectively. All the interface functions can take an unlimited number of input arguments if compiled with C++11 variadic templates support. In C++98 mode, the number of arguments must be limited to some fixed upper bound which is currently 16 as of version 1.3. Supporting more arguments is quite easy using the in-source code generator based on cog.py - see the source for details.

The format() function which takes a stream as the first argument is the main part of the tinyformat interface. stream is the output stream, formatString is a format string in C99 printf() format, and the values to be formatted have arbitrary types:

template<typename... Args>
void format(std::ostream& stream, const char* formatString,
            const Args&... args);

The second version of format() is a convenience function which returns a std::string rather than printing onto a stream. This function simply calls the main version of format() using a std::ostringstream, and returns the resulting string:

template<typename... Args>
std::string format(const char* formatString, const Args&... args);

Finally, printf() and printfln() are convenience functions which call format() with std::cout as the first argument; both have the same signature:

template<typename... Args>
void printf(const char* formatString, const Args&... args);

printfln() is the same as printf() but appends an additional newline for convenience - a concession to the author's tendency to forget the newline when using the library for simple logging.

Format strings and type safety

Tinyformat parses C99 format strings to guide the formatting process --- please refer to any standard C99 printf documentation for format string syntax. In contrast to printf, tinyformat does not use the format string to decide on the type to be formatted so this does not compromise the type safety: you may use any format specifier with any C++ type. The author suggests standardising on the %s conversion unless formatting numeric types.

Let's look at what happens when you execute the function call:

tfm::format(outStream, "%+6.4f", yourType);

First, the library parses the format string, and uses it to modify the state of outStream:

  1. The outStream formatting flags are cleared and the width, precision and fill reset to the default.
  2. The flag '+' means to prefix positive numbers with a '+'; tinyformat executes outStream.setf(std::ios::showpos)
  3. The number 6 gives the field width; execute outStream.width(6).
  4. The number 4 gives the precision; execute outStream.precision(4).
  5. The conversion specification character 'f' means that floats should be formatted with a fixed number of digits; this corresponds to executing outStream.setf(std::ios::fixed, std::ios::floatfield);

After all these steps, tinyformat executes:

outStream << yourType;

and finally restores the stream flags, precision and fill.

What happens if yourType isn't actually a floating point type? In this case the flags set above are probably irrelevant and will be ignored by the underlying std::ostream implementation. The field width of six may cause some padding in the output of yourType, but that's about it.

Special cases for "%p", "%c" and "%s"

Tinyformat normally uses operator<< to convert types to strings. However, the "%p" and "%c" conversions require special rules for robustness. Consider:

uint8_t* pixels = get_pixels(/* ... */);
tfm::printf("%p", pixels);

Clearly the intention here is to print a representation of the pointer to pixels, but since uint8_t is a character type the compiler would attempt to print it as a C string if we blindly fed it into operator<<. To counter this kind of madness, tinyformat tries to static_cast any type fed to the "%p" conversion into a const void* before printing. If this can't be done at compile time the library falls back to using operator<< as usual.

The "%c" conversion has a similar problem: it signifies that the given integral type should be converted into a char before printing. The solution is identical: attempt to convert the provided type into a char using static_cast if possible, and if not fall back to using operator<<.

The "%s" conversion sets the boolalpha flag on the formatting stream. This means that a bool variable printed with "%s" will come out as true or false rather than the 1 or 0 that you would otherwise get.

Incompatibilities with C99 printf

Not all features of printf can be simulated simply using standard iostreams. Here's a list of known incompatibilities:

  • The "%a" and "%A" hexadecimal floating point conversions ignore precision as stream output of hexfloat (introduced in C++11) ignores precision, always outputting the minimum number of digits required for exact representation. MSVC incorrectly honors stream precision, so we force precision to 13 in this case to guarentee lossless roundtrip conversion.
  • The precision for integer conversions cannot be supported by the iostreams state independently of the field width. (Note: this is only a problem for certain obscure integer conversions; float conversions like %6.4f work correctly.) In tinyformat the field width takes precedence, so the 4 in %6.4d will be ignored. However, if the field width is not specified, the width used internally is set equal to the precision and padded with zeros on the left. That is, a conversion like %.4d effectively becomes %04d internally. This isn't correct for every case (eg, negative numbers end up with one less digit than desired) but it's about the closest simple solution within the iostream model.
  • The "%n" query specifier isn't supported to keep things simple and will result in a call to TINYFORMAT_ERROR.
  • The "%ls" conversion is not supported, and attempting to format a wchar_t array will cause a compile time error to minimise unexpected surprises. If you know the encoding of your wchar_t strings, you could write your own std::ostream insertion operator for them, and disable the compile time check by defining the macro TINYFORMAT_ALLOW_WCHAR_STRINGS. If you want to print the address of a wide character with the "%p" conversion, you should cast it to a void* before passing it to one of the formatting functions.

Error handling

By default, tinyformat calls assert() if it encounters an error in the format string or number of arguments. This behaviour can be changed (for example, to throw an exception) by defining the TINYFORMAT_ERROR macro before including tinyformat.h, or editing the config section of the header.

Formatting user defined types

User defined types with a stream insertion operator will be formatted using operator<<(std::ostream&, T) by default. The "%s" format specifier is suggested f

View on GitHub
GitHub Stars560
CategoryDevelopment
Updated16d ago
Forks77

Languages

C++

Security Score

85/100

Audited on Mar 9, 2026

No findings