Code Monkey home page Code Monkey logo

tinyformat's Introduction

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 for user defined types, unless the type is inherently numeric.

For further customization, the user can override the formatValue() function to specify formatting independently of the stream insertion operator. If you override this function, the library will have already parsed the format specification and set the stream flags accordingly - see the source for details.

Wrapping tfm::format() inside a user defined format function

Suppose you wanted to define your own function which wraps tfm::format. For example, consider an error function taking an error code, which in C++11 might be written simply as:

template<typename... Args>
void error(int code, const char* fmt, const Args&... args)
{
    std::cerr << "error (code " << code << ")";
    tfm::format(std::cerr, fmt, args...);
}

Simulating this functionality in C++98 is pretty painful since it requires writing out a version of error() for each desired number of arguments. To make this bearable tinyformat comes with a set of macros which are used internally to generate the API, but which may also be used in user code.

The three macros TINYFORMAT_ARGTYPES(n), TINYFORMAT_VARARGS(n) and TINYFORMAT_PASSARGS(n) will generate a list of n argument types, type/name pairs and argument names respectively when called with an integer n between 1 and 16. We can use these to define a macro which generates the desired user defined function with n arguments. This should be followed by a call to TINYFORMAT_FOREACH_ARGNUM to generate the set of functions for all supported n:

#define MAKE_ERROR_FUNC(n)                                    \
template<TINYFORMAT_ARGTYPES(n)>                              \
void error(int code, const char* fmt, TINYFORMAT_VARARGS(n))  \
{                                                             \
    std::cerr << "error (code " << code << ")";               \
    tfm::format(std::cerr, fmt, TINYFORMAT_PASSARGS(n));      \
}
TINYFORMAT_FOREACH_ARGNUM(MAKE_ERROR_FUNC)

Sometimes it's useful to be able to pass a list of format arguments through to a non-template function. The FormatList class is provided as a way to do this by storing the argument list in a type-opaque way. For example:

template<typename... Args>
void error(int code, const char* fmt, const Args&... args)
{
    tfm::FormatListRef formatList = tfm::makeFormatList(args...);
    errorImpl(code, fmt, formatList);
}

What's interesting here is that errorImpl() is a non-template function so it could be separately compiled if desired. The FormatList instance can be used via a call to the vformat() function (the name chosen for semantic similarity to vprintf()):

void errorImpl(int code, const char* fmt, tfm::FormatListRef formatList)
{
    std::cerr << "error (code " << code << ")";
    tfm::vformat(std::cout, fmt, formatList);
}

The construction of a FormatList instance is very lightweight - it defers all formatting and simply stores a couple of function pointers and a value pointer per argument. Since most of the actual work is done inside vformat(), any logic which causes an early exit of errorImpl() - filtering of verbose log messages based on error code for example - could be a useful optimization for programs using tinyformat. (A faster option would be to write any early bailout code inside error(), though this must be done in the header.)

Benchmarks

Compile time and code bloat

The script bloat_test.sh included in the repository tests whether tinyformat succeeds in avoiding compile time and code bloat for nontrivial projects. The idea is to include tinyformat.h into 100 translation units and use printf() five times in each to simulate a medium sized project. The resulting executable size and compile time (g++-4.8.2, linux ubuntu 14.04) is shown in the following tables, which can be regenerated using make bloat_test:

Non-optimized build

test name compiler wall time executable size (stripped)
libc printf 1.8s 48K (36K)
std::ostream 10.7s 96K (76K)
tinyformat, no inlines 18.9s 140K (104K)
tinyformat 21.1s 220K (180K)
tinyformat, c++0x mode 20.7s 220K (176K)
boost::format 70.1s 844K (736K)

Optimized build (-O3 -DNDEBUG)

test name compiler wall time executable size (stripped)
libc printf 2.3s 40K (28K)
std::ostream 11.8s 104K (80K)
tinyformat, no inlines 23.0s 128K (104K)
tinyformat 32.9s 128K (104K)
tinyformat, c++0x mode 34.0s 128K (104K)
boost::format 147.9s 644K (600K)

For large projects it's arguably worthwhile to do separate compilation of the non-templated parts of tinyformat, as shown in the rows labelled tinyformat, no inlines. These were generated by putting the implementation of vformat (detail::formatImpl() etc) it into a separate file, tinyformat.cpp. Note that the results above can vary considerably with different compilers. For example, the -fipa-cp-clone optimization pass in g++-4.6 resulted in excessively large binaries. On the other hand, the g++-4.8 results are quite similar to using clang++-3.4.

Speed tests

The following speed tests results were generated by building tinyformat_speed_test.cpp on an Intel core i7-2600K running Linux Ubuntu 14.04 with g++-4.8.2 using -O3 -DNDEBUG. In the test, the format string "%0.10f:%04d:%+g:%s:%p:%c:%%\n" is filled 2000000 times with output sent to /dev/null; for further details see the source and Makefile.

test name run time
libc printf 1.20s
std::ostream 1.82s
tinyformat 2.08s
boost::format 9.04s

It's likely that tinyformat has an advantage over boost.format because it tries reasonably hard to avoid formatting into temporary strings, preferring instead to send the results directly to the stream buffer. Tinyformat cannot be faster than the iostreams because it uses them internally, but it comes acceptably close.

Rationale

Or, why did I reinvent this particularly well studied wheel?

Nearly every program needs text formatting in some form but in many cases such formatting is incidental to the main purpose of the program. In these cases, you really want a library which is simple to use but as lightweight as possible.

The ultimate in lightweight dependencies are the solutions provided by the C++ and C libraries. However, both the C++ iostreams and C's printf() have well known usability problems: iostreams are hopelessly verbose for complicated formatting and printf() lacks extensibility and type safety. For example:

// Verbose; hard to read, hard to type:
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
// The alternative using a format string is much easier on the eyes
tfm::printf("%.2f\n", 1.23456);

// Type mismatch between "%s" and int: will cause a segfault at runtime!
printf("%s", 1);
// The following is perfectly fine, and will result in "1" being printed.
tfm::printf("%s", 1);

On the other hand, there are plenty of excellent and complete libraries which solve the formatting problem in great generality (boost.format and fastformat come to mind, but there are many others). Unfortunately these kind of libraries tend to be rather heavy dependencies, far too heavy for projects which need to do only a little formatting. Problems include

  1. Having many large source files. This makes a heavy dependency unsuitable to bundle within other projects for convenience.
  2. Slow build times for every file using any sort of formatting (this is very noticeable with g++ and boost/format.hpp. I'm not sure about the various other alternatives.)
  3. Code bloat due to instantiating many templates

Tinyformat tries to solve these problems while providing formatting which is sufficiently general and fast for incidental day to day uses.

License

For minimum license-related fuss, tinyformat.h is distributed under the boost software license, version 1.0. (Summary: you must keep the license text on all source copies, but don't have to mention tinyformat when distributing binaries.)

Author and acknowledgements

Tinyformat is written and maintained by Chris Foster, with various contributions gratefully recieved from the community.

Originally the implementation was inspired by the way boost::format uses stream based formatting to simulate most of the printf() syntax, and Douglas Gregor's toy printf() in an early variadic template example.

Bugs

Here's a list of known bugs which are probably cumbersome to fix:

  • Field padding won't work correctly with complicated user defined types. For general types, the only way to do this correctly seems to be format to a temporary string stream, check the length, and finally send to the output stream with padding if necessary. Doing this for all types would be quite inelegant because it implies extra allocations to make the temporary stream. A workaround is to add logic to operator<<() for composite user defined types so they are aware of the stream field width.

tinyformat's People

Contributors

arsenm avatar beemaster avatar c42f avatar dakep avatar evandrocoan avatar fanquake avatar jrohel avatar kainjow avatar krlmlr avatar lanurmi avatar lgritz avatar nigels-com avatar pkunavin avatar rex4539 avatar willwray avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tinyformat's Issues

error: invalid static_cast from type ‘const void*’ to type ‘void (*)()’

#include<iostream>
#include "tinyformat.h"

void fun_void_void(){};

int main(void) {
    std::cout << std::boolalpha;
    std::cout << fun_void_void << std::endl;
    std::cout << tfm::format( "1. %s", fun_void_void ) << std::endl;
    return 0;
}

Calling std::cout << fun_void_void works fine, but tfm::format( "1. %s", fun_void_void ) (either %s or %p) results in:

$ g++ -o main.exe -g -ggdb test_debugger4.cpp --std=c++11 && ./main.exe
In file included from test_debugger4.cpp:2:0:
tinyformat.h: In instantiation of ‘static void tinyformat::detail::FormatArg::formatImpl(std::ostream&, const char*, const char*, int, const void*) [with T = void(); std::ostream = std::basic_ostream<char>]’:
tinyformat.h:545:38:   required from ‘tinyformat::detail::FormatArg::FormatArg(const T&) [with T = void()]’
tinyformat.h:1000:32:   required from ‘tinyformat::detail::FormatListN<N>::FormatListN(const Args& ...) [with Args = {void()}; int N = 1]’
tinyformat.h:1053:20:   required from ‘tinyformat::detail::FormatListN<sizeof... (Args)> tinyformat::makeFormatList(const Args& ...) [with Args = {void()}]’
tinyformat.h:1089:37:   required from ‘void tinyformat::format(std::ostream&, const char*, const Args& ...) [with Args = {void()}; std::ostream = std::basic_ostream<char>]’
tinyformat.h:1098:11:   required from ‘std::string tinyformat::format(const char*, const Args& ...) [with Args = {void()}; std::string = std::basic_string<char>]’
test_debugger4.cpp:9:54:   required from here
tinyformat.h:568:57: error: invalid static_cast from type ‘const void*’ to type ‘void (*)()’
             formatValue(out, fmtBegin, fmtEnd, ntrunc, *static_cast<const T*>(value));
                                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
tinyformat.h: In instantiation of ‘static int tinyformat::detail::FormatArg::toIntImpl(const void*) [with T = void()]’:
tinyformat.h:545:38:   required from ‘tinyformat::detail::FormatArg::FormatArg(const T&) [with T = void()]’
tinyformat.h:1000:32:   required from ‘tinyformat::detail::FormatListN<N>::FormatListN(const Args& ...) [with Args = {void()}; int N = 1]’
tinyformat.h:1053:20:   required from ‘tinyformat::detail::FormatListN<sizeof... (Args)> tinyformat::makeFormatList(const Args& ...) [with Args = {void()}]’
tinyformat.h:1089:37:   required from ‘void tinyformat::format(std::ostream&, const char*, const Args& ...) [with Args = {void()}; std::ostream = std::basic_ostream<char>]’
tinyformat.h:1098:11:   required from ‘std::string tinyformat::format(const char*, const Args& ...) [with Args = {void()}; std::string = std::basic_string<char>]’
test_debugger4.cpp:9:54:   required from here
tinyformat.h:574:45: error: invalid static_cast from type ‘const void*’ to type ‘void (*)()’
             return convertToInt<T>::invoke(*static_cast<const T*>(value));
                                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

printf issue with VS2013 x64

MSVC is generating format errors for ints when using tiny format::printf in Visual Studio 2013 Update 3 targeting x64 using this code:

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);
warning C4313: 'printf' : '%d' in format string conflicts with argument 3 of type 'const size_t *'

I get this warning for each int. If I build targeting 32-bit it works fine. I'm not sure what's going on. It seems to think the argument type is a pointer. I wonder if this is a bug?

If I use tfm::format to generate a string, it works fine.

Speed tests: add test for largest data

    #define str \
        "asdcf'awsrk.fw3[045im2,[4oc'wxe;/fzd1[-,c4rxj.wk;szlcgjx34-rx134rxqwetaertg" \
        "asdcf'awsrk.fw3[045im2,[4oc'wxe;/fzd1[-,c4rxj.wk;szlcgjx34-rx134rxqwetaertg" \
        "asdcf'awsrk.fw3[045im2,[4oc'wxe;/fzd1[-,c4rxj.wk;szlcgjx34-rx134rxqwetaertg" \
        "asdcf'awsrk.fw3[045im2,[4oc'wxe;/fzd1[-,c4rxj.wk;szlcgjx34-rx134rxqwetaertg" \
        "asdcf'awsrk.fw3[045im2,[4oc'wxe;/fzd1[-,c4rxj.wk;szlcgjx34-rx134rxqwetaertg" \
        "asdcf'awsrk.fw3[045im2,[4oc'wxe;/fzd1[-,c4rxj.wk;szlcgjx34-rx134rxqwetaertg" \
        "asdcf'awsrk.fw3[045im2,[4oc'wxe;/fzd1[-,c4rxj.wk;szlcgjx34-rx134rxqwetaertg" \
        "asdcf'awsrk.fw3[045im2,[4oc'wxe;/fzd1[-,c4rxj.wk;szlcgjx34-rx134rxqwetaertg" \
        "asdcf'awsrk.fw3[045im2,[4oc'wxe;/fzd1[-,c4rxj.wk;szlcgjx34-rx134rxqwetaertg" \
        "asdcf'awsrk.fw3[045im2,[4oc'wxe;/fzd1[-,c4rxj.wk;szlcgjx34-rx134rxqwetaertg"
...
    } else if (which == "tinyformat") {
        for (long i = 0; i < loops; ++ i) {
            tfm::printf("%0.10f" str "%04d" str "%+g" str "%s" str "%p" str "%c" str "%%\n",
                1.234, 42, 3.13, "string", (void *)1000, (int)'X');
        }
    }
...

Usage question: How to NOT display a parameter?

Consider:

tfm::format(
   _(
     "%1$s the pig!",
     "%1$s all of the %2$d pigs!",
     number_of_pigs
     ),
   "Floop",
   number_of_pigs
)

where _() is i18n macro which returns translated singular/plural string depending on number_of_pigs.
In the case of a single pig, i would want a sentence not containing the number 1.
Now this won't work because

Not enough conversion specifiers in format string

Tried using zero-width specifier like %1$00d or using %n to hide the 1.
Is there a way to hide a parameter?

bloat with newer gcc version

gcc-4.6.3 now produces quite a bloated build with optimizations on:

./bloat_test.sh -O3 -DUSE_TINYFORMAT

real 0m36.515s
user 0m34.010s
sys 0m1.972s
1020K bloat_test_tmp.out
972K bloat_test_tmp.out

(checked also with version used for the original bloat tests, and it's even worse). This used to be only 300K or so with version 4.4 - presumably gcc is now inlining a lot more aggressively :-(

Still not as bad as boost.format, but by no means great either.

Missing include for std::min

In the latest version of tinyformat it makes use of std::min which is defined in <algorithm> but that header has not been included.

For some reason gcc/clang/mingw do not seem to care but msvc does and refuses to compile.

Segmentation Fault with Intel Compiler

I came across a very strange problem in tinyformat v2.2.0. Even the most trivial of examples

#include "tinyformat.h"

int main(int argc, char** argv) {
  auto str = tfm::format("Got %d arguments", argc);
  return str.size();
}

causes a segfault when compiling with icpc -std=gnu++11 -O0. There is no segfault when compiling with any optimization level greater than 0.
The backtrace is

#0  0x00007fffffff9808 in ?? ()
#1  0x000000000040216f in tinyformat::detail::FormatArg::format (this=0x7fffffff9768, out=..., fmtBegin=0x404738 "%d arguments", fmtEnd=0x40473a " arguments", ntrunc=-1) at tinyformat.h:513
#2  0x0000000000403994 in tinyformat::detail::formatImpl (out=..., fmt=0x404738 "%d arguments", formatters=0x7fffffff9768, numFormatters=1) at tinyformat.h:812
#3  0x0000000000403ec6 in tinyformat::vformat (out=..., fmt=0x404734 "Got %d arguments", list=...) at tinyformat.h:956
#4  0x0000000000403fb5 in tinyformat::format<int> (out=..., fmt=0x404734 "Got %d arguments", args=@0x7fffffff99a8: 1) at tinyformat.h:966
#5  0x0000000000403f49 in tinyformat::format<int> (fmt=0x404734 "Got %d arguments", args=@0x7fffffff99a8: 1) at tinyformat.h:975
#6  0x00000000004018e2 in main (argc=1, argv=0x7fffffff9ac8) at tfm_bug.cc:4

I tried with two different versions of icpc

  • icpc (ICC) 18.0.3 20180410 on RHEL7 (3.10.0-957.27.2.el7.x86_64)
  • icpc (ICC) 19.0.5.281 20190815 RHEL7 (3.10.0-1062.1.2.el7.x86_64)

Update minimum CMake version

CMake 3.25.2 gives this deprecation warning:

CMake Deprecation Warning at [tinyformat]/CMakeLists.txt:21 (cmake_minimum_required):
Compatibility with CMake < 2.8.12 will be removed from a future version of
CMake.

Update the VERSION argument value or use a ... suffix to tell
CMake that the project does not need compatibility with older versions.

Linker warning on OS X

I have a linker warning on OS X:

ld: warning: direct access in tinyformat::detail::FormatArg::FormatArg(unsigned long const&) to global weak symbol void tinyformat::detail::FormatArg::formatImpl(std::__1::basic_ostream<char, std::1::char_traits >&, char const, char const, int, void const*) means the weak symbol cannot be overridden at runtime. This was likely caused by different translation units being compiled with different visibility settings.

Do you have an idea how to fix it?

Parse format string at compile time with constexpr to determine number of arguments

With constexpr it seems to be possible to parse the format string at compile time to determine if the number of arguments passed actually matches the number of arguments specified.

This could fill the void when moving from built-in printfs that have compile support (via attribute).

Links:
http://akrzemi1.wordpress.com/2011/05/11/parsing-strings-at-compile-time-part-i/
http://abel.web.elte.hu/mpllibs/safe_printf/

Issues found during tinyformat audit (part of Bitcoin Core audit)

Hi all,

First, thanks for creating the tinyformat library. Having an easy-to-use locale independent formatting library available under a permissive license is really nice! :)

Bitcoin Core uses tinyformat for formatting of log messages. While auditing Bitcoin Core I discovered the following issues in tinyformat that I thought were worth reporting upstreams.

All issues have been verified against current master.

Issue 1. The following causes a signed integer overflow and a subsequent allocation of 9 GB of RAM (or an OOM in memory constrained environments):

tfm::format("%.777777700000000$", 1.0);

Issue 2. The following causes a stack overflow:

tfm::format("%987654321000000:", 1);

Issue 3. The following causes a stack overflow:

tfm::format("%1$*1$*", -11111111);

Issue 4. The following causes a NULL pointer dereference:

tfm::format("%.1s", (char *)nullptr);

Issue 5. The following causes a float cast overflow:

tfm::format("%c", -1000.0);

Issue 6. The following causes a float cast overflow followed by an invalid integer negation:

tfm::format("%*", std::numeric_limits<double>::lowest());

Note that I've only audited tfm::format(…, …) which is the part of tinyformat used by Bitcoin Core.

Thanks for a nice library!

Support CString in VC

Now I use following code in VC2010:

#include "tinyformat.h"
#define MAKE_CSTRING_FORMAT_FUNC(n) \
template<TINYFORMAT_ARGTYPES(n)>    \
CString csFormat(const char* fmt, TINYFORMAT_VARARGS(n))    \
{   \
    std::string m = tfm::format(fmt, TINYFORMAT_PASSARGS(n));   \
    return CString(m.c_str());  \
}
TINYFORMAT_FOREACH_ARGNUM(MAKE_CSTRING_FORMAT_FUNC)

Call it by:

CString sMsg = csFormat("%i %s", i, s);

Template program to count the number of format specifiers and check if they match the arguments count

When some formatting function as "Hi %s %s.", "Hi" is used, it will throw a run-time error. If I understand correctly, C++ templates are Turing Complete, so they can do any computation.

Then, if they can do any computation, why they cannot count how many times I used the format specifiers Hi %s %s in my string, and check if this is equal to the number of parameters "Hi" passed to the formatting function?

iostream version of speed_test produces different output

First thanks for a great library.

I've notices a minor issue in speed tests. To produce the same output as other methods, iostream version in tinyformat_test.cpp should be as follows:

        std::cout << std::setprecision(10) << std::fixed << 1.234 << ":"
            << std::resetiosflags(std::ios::floatfield)
            << std::setw(4) << std::setfill('0') << 42 << std::setfill(' ') << ":"
            << std::setiosflags(std::ios::showpos) << 3.13 << std::resetiosflags(std::ios::showpos) << ":"
            << "str" << ":"
            << (void*)1000 << ":"
            << 'X' << ":%\n";

Note added std::fixed and std::resetiosflags(std::ios::floatfield). This might have a small impact on the results of a speed test.

executable size is too large

header only is not an advantage for safe printf, A more efficient implementation should just use variadic template to compute a type-signature-array, and pass to an external_linkage_safe_printf function along with other parameters

handling the 0-variable frmat

In your code I see:

//------------------------------------------------------------------------------
// Private format function on top of which the public interface is implemented.
// We enforce a mimimum of one value to be formatted to prevent bugs looking like
//
//   const char* myStr = "100% broken";
//   printf(myStr);   // Parses % as a format specifier

And I understand why. However, having a version that allows it and putting trust back to the programmer is more useful to me.

In Rcpp11,
https://github.com/romainfrancois/Rcpp11/blob/master/inst/include/Rcpp/utils/tinyformat.h @kevinushey and I have modified it so that it handles the case where there a no variable to format.

The alternative would have been to come up with dispatch based on the sizeof...(Args) in or code.

Anyway, what I've done is this:

template <typename... Args> struct Formatter{
  inline void operator()(FormatIterator& fmtIter){
    fmtIter.finish() ;
  }
} ;

template <typename T1, typename... Args>
struct Formatter<T1, Args...>{
  inline void operator()( FormatIterator& fmtIter, const T1& value1, const Args&... args){
    fmtIter.accept(value1);
    Formatter<Args...>()(fmtIter, args...);  
  }
} ;

If you strongly believe that this case should not be handled, that's fine. Otherwise I can prepare a pull request. (this is more work I suppose because in Rcpp11 we only kept the C++11 part as we assume C++11 support.

BTW, would it make sense to provide a C++11 only variant of tinyformat for those who can assume C++11.

Cannot print function pointer

#include "tinyformat.h"

int main(int argc, char const *argv[])
{
    // typedef int (*Function)( int argc, char const *argv[] );
    std::cerr << tfm::format( "var2 %s", &main ) << std::endl;
    return 0;
}

Results in:

# g++ -o main -g -ggdb test_debugger.cpp --std=c++98 && ./main
var2 true

If I remove the &main and use only main, it does not compile:

# g++ -o main -g -ggdb test_debugger.cpp ---std=c++98 && ./main
In file included from test_debugger.cpp:25:0:
tinyformat.h: In instantiation of ‘static void tinyformat::detail::FormatArg::formatImpl(std::ostream&, const char*, const char*, int, const void*) [with T = int(int, const char**); std::ostream = std::basic_ostream<char>]’:
tinyformat.h:499:38:   required from ‘tinyformat::detail::FormatArg::FormatArg(const T&) [with T = int(int, const char**)]’
tinyformat.h:976:9:   required from ‘void tinyformat::detail::FormatListN<N>::init(int, const T0&) [with T0 = int(int, const char**); int N = 1]’
tinyformat.h:976:9:   required from ‘tinyformat::detail::FormatListN<N>::FormatListN(const T0&) [with T0 = int(int, const char**); int N = 1]’
tinyformat.h:1025:1:   required from ‘tinyformat::detail::FormatListN<1> tinyformat::makeFormatList(const T1&) [with T1 = int(int, const char**)]’
tinyformat.h:1108:1:   required from ‘void tinyformat::format(std::ostream&, const char*, const T1&) [with T1 = int(int, const char**); std::ostream = std::basic_ostream<char>]’
tinyformat.h:1108:1:   required from ‘std::__cxx11::string tinyformat::format(const char*, const T1&) [with T1 = int(int, const char**); std::__cxx11::string = std::__cxx11::basic_string<char>]’
test_debugger.cpp:30:47:   required from here
tinyformat.h:522:57: error: invalid static_cast from type ‘const void*’ to type ‘int (*)(int, const char**)’
             formatValue(out, fmtBegin, fmtEnd, ntrunc, *static_cast<const T*>(value));
                                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
tinyformat.h: In instantiation of ‘static int tinyformat::detail::FormatArg::toIntImpl(const void*) [with T = int(int, const char**)]’:
tinyformat.h:499:38:   required from ‘tinyformat::detail::FormatArg::FormatArg(const T&) [with T = int(int, const char**)]’
tinyformat.h:976:9:   required from ‘void tinyformat::detail::FormatListN<N>::init(int, const T0&) [with T0 = int(int, const char**); int N = 1]’
tinyformat.h:976:9:   required from ‘tinyformat::detail::FormatListN<N>::FormatListN(const T0&) [with T0 = int(int, const char**); int N = 1]’
tinyformat.h:1025:1:   required from ‘tinyformat::detail::FormatListN<1> tinyformat::makeFormatList(const T1&) [with T1 = int(int, const char**)]’
tinyformat.h:1108:1:   required from ‘void tinyformat::format(std::ostream&, const char*, const T1&) [with T1 = int(int, const char**); std::ostream = std::basic_ostream<char>]’
tinyformat.h:1108:1:   required from ‘std::__cxx11::string tinyformat::format(const char*, const T1&) [with T1 = int(int, const char**); std::__cxx11::string = std::__cxx11::basic_string<char>]’
test_debugger.cpp:30:47:   required from here
tinyformat.h:528:45: error: invalid static_cast from type ‘const void*’ to type ‘int (*)(int, const char**)’
             return convertToInt<T>::invoke(*static_cast<const T*>(value));
                                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

Example using a volatile variable:

#include "tinyformat.h"

int main(int argc, char const *argv[])
{
    typedef int (*Function)( int argc, char const *argv[] );
    volatile Function var1 = reinterpret_cast< volatile Function >( main );

    std::cerr << tfm::format( "volatile var1 %p", var1 ) << std::endl;
    std::cerr << tfm::format( "volatile var1 %s", var1 ) << std::endl;
    return 0;
}

Results in the same

# g++ -o main -g -ggdb test_debugger.cpp --std=c++98 && ./main
volatile var1 1
volatile var1 true

Different output for earlier compilers

The following code will print different output on different compilers:

enum Number : uint8_t {
        FOO = 111, 
};

int main(){
    std::cout << tinyformat::format("%s", Number::FOO) << std::endl;
}

gcc 9 (and earlier): 111
gcc 10 (and later): o
clang 9 (and earlier): 111
gcc 10 (and later): o

Consider printfln() convenience function

I'm considering adding a printfln() function for convenience which simply appends a "\n" to the usual printf() output. This would be especially useful for ad hoc debug logging. Naming wise, this is consistent with some other languages such as D which has writef/writefln. No obvious precedent in the C and C++ world come to mind.

On the downside, it's a new public API for something that's trivial to do with plain old printf("foo\n"), but sometimes I still forget the newline, especially when using alongside a logging API which appends newlines automatically.

There's a small but nonzero number of people watching tinyformat - thoughts anyone?

Ability to format sequentially

Would it be possible to have the following feature:

auto op = tfm::formatOp("%i , %i ");
op % a;
op % b;
op % c ; // this would result in an assert or exception

Thanks for considering this :-), this makes sequential parsing in values and converting them into a string extremely easy :-)

Wrapping support for constructor initialization lists

It would be handy to be able to wrap constructors, so in particular we can easily write a MyError which extends from std::runtime_error and supports something like:

if (bar > barMax)
    throw MyError("foo failed because bar=%d is greater than %d", bar, barMax);

Granted, this is already possible in a roundabout way if you're happy to extend std::exception instead.

Alter repository title or decouple from standard streams

The repository title is "A minimal type safe printf() replacement"... but it really isn't minimal if you pull in all of <iostream> ... you could have just wrapped printf (or even decoupled the underlying "real-output" function and make this wrapper more abstract). Right now the title is misleading, so I suggest it be rephrased.

Otherwise - nice little library... I was just hoping to be able to use it where the standard library streams are not available.

Cannot print volatile variables: error: invalid static_cast from type const volatile unsigned int* to type const void*

volatile_cast_test.cpp

#include "tinyformat.h"

int main(int argc, char const *argv[])
{
    volatile unsigned int wait_time = 10;
    std::cerr << tfm::format( "Volatile '%s'\n", wait_time ) << std::endl;
    return 0;
}

g++ -o main volatile_cast_test.cpp && ./main

In file included from volatile_cast_test.cpp:2:0:
tinyformat.h: In instantiation of ‘tinyformat::detail::FormatArg::FormatArg(const T&) [with T = volatile unsigned int]’:
tinyformat.h:966:32:   required from ‘tinyformat::detail::FormatListN<N>::FormatListN(const Args& ...) [with Args = {volatile unsigned int}; int N = 1]’
tinyformat.h:1019:20:   required from ‘tinyformat::detail::FormatListN<sizeof... (Args)> tinyformat::makeFormatList(const Args& ...) [with Args = {volatile unsigned int}]’
tinyformat.h:1055:37:   required from ‘void tinyformat::format(std::ostream&, const char*, const Args& ...) [with Args = {volatile unsigned int}; std::ostream = std::basic_ostream<char>]’
tinyformat.h:1064:11:   required from ‘std::__cxx11::string tinyformat::format(const char*, const Args& ...) [with Args = {volatile unsigned int}; std::__cxx11::string = std::__cxx11::basic_string<char>]’
volatile_cast_test.cpp:7:60:   required from here
tinyformat.h:509:23: error: invalid static_cast from type ‘const volatile unsigned int*’ to type ‘const void*’
             : m_value(static_cast<const void*>(&value)),

Compile error is vc2010

// Format at most ntrunc characters to the given stream. template<typename T> inline void formatTruncated(std::ostream& out, const T& value, int ntrunc) { std::ostringstream tmp; tmp << value; std::string result = tmp.str(); out.write(result.c_str(), std::min(ntrunc, static_cast<int>(result.size()))); }

"std::min" should be changed to "min"

Formatting NaN or infinity with explicit precision injects spurious zeros

When I format e.g., double NaN or infinity values and explicitly request precision (e.g., %.16d), spurious 0 characters are injected into the output. Since NaN or infinity values are probably never formatted as digits I would expect them to not be present at all.

Examples:

CHECK_EQUAL(tfm::format("%.4g", std::numeric_limits<double>::infinity()), "inf");
CHECK_EQUAL(tfm::format("%.0d", std::numeric_limits<double>::infinity()), "inf");
CHECK_EQUAL(tfm::format("%.1d", std::numeric_limits<double>::infinity()), "inf");
CHECK_EQUAL(tfm::format("%.2d", std::numeric_limits<double>::infinity()), "inf");
CHECK_EQUAL(tfm::format("%.3d", std::numeric_limits<double>::infinity()), "inf");
CHECK_EQUAL(tfm::format("%.4d", std::numeric_limits<double>::infinity()), "inf"); // returns `0inf`, fails for this and larger n.
CHECK_EQUAL(tfm::format("%.4g", std::numeric_limits<double>::quiet_NaN()), "nan");
CHECK_EQUAL(tfm::format("%.0d", std::numeric_limits<double>::quiet_NaN()), "nan");
CHECK_EQUAL(tfm::format("%.1d", std::numeric_limits<double>::quiet_NaN()), "nan");
CHECK_EQUAL(tfm::format("%.2d", std::numeric_limits<double>::quiet_NaN()), "nan");
CHECK_EQUAL(tfm::format("%.3d", std::numeric_limits<double>::quiet_NaN()), "nan");
CHECK_EQUAL(tfm::format("%.4d", std::numeric_limits<double>::quiet_NaN()), "nan"); // returns `0nan`, fails for this and larger n.

Warning in XCode 7

After updating to XCode 7 I have following warning:

tinyformat.h:864:27: error: field 'm_formatterStore' is uninitialized when used here [-Werror,-Wuninitialized]
            : FormatList(&m_formatterStore[0], N),

I wish I could do a pull request but I can not wrap my head around how this could be fixed.

Support for POSIX extension of positional arguments

I was looking for simple formating string support for C++ with the ability to rearrange formating arguments.

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

I found tinyformat but it does not support rearrange formatting arguments. Without this support the tinyformat is not usable in projects where localization is required.

So, I had choices: Search for another library or write new one or improve tinyformat.
I decided to improve tinyformat and I made PR #45 wich adds support for POSIX extension of positional arguments.

I think tinyformat will be usable for more users and projects with my PR.
Thanks for tinyformat. Have a nice day.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.