Coder Social home page Coder Social logo

pprintpp's Introduction

Python style printf for C++

Build Status (GCC and Clang)

Maintenance status: No active development is done here because the library is "complete". It is still actively used. If you happen to run into a bug, we'll get it sorted out.

What is pprintpp?

The acronym stands for "Python style print for C plus plus".

pprintpp is a header-only C++ library which aims to make printf use safe and easy. It is a pure compile time library and will add no overhead to the runtime of your programs.

This library is for everyone who uses C++ but sticks to printf-like functions (like printf, fprintf, sprintf, snprintf, etc...). pprintpp adds a typesafe adapter on top of those functions by preprocessing strings to the format printf and its friends are expecting. Apart from the preformatted string, no other symbols are added to the resulting binary. This means that this library produces no runtime code at all, which distinguishes it from libraries like fmtlib (There has been some controversy in the comparison with fmt - look into the FAQ in this document on that matter please).

Dependencies

The library does only depend on C++11 (or higher) and the STL (<tuple> and <type_traits>).

The STL dependency can easily get rid of by reimplementing some type traits. This way pprintpp can be used in hardcore baremetal environments (where it already has been in use actually).

Example

When using printf, the programmer has to choose the right types in the format string.

printf("An int %d, a float %f, a string %s\n", 123, 7.89, "abc");

If this format string is wrong, the programm will misformat something in the best case. In the worst case, the program might even crash.

The python style print library allows for the following:

pprintf("An int {}, a float {}, a string {s}\n", 123, 7.89, "abc");

The types are chosen automatically at compile time. This is both safe and convenient.

Note the {s} in the format string: It is a safety detail of the library to require an additional "s" in the format string to print char* types really as strings. Otherwise, every char* would be printed as string, although it might be some non-null-terminated buffer.

The assembly generated from the simple program...

int main()
{
    pprintf("{} hello {s}! {}\n", 1, "world", 2);
}

...shows that this library comes with no runtime overhead:

bash $ objdump -d example
...
0000000000400450 <main>:
  400450:       48 83 ec 08             sub    $0x8,%rsp
  400454:       41 b8 02 00 00 00       mov    $0x2,%r8d
  40045a:       b9 04 06 40 00          mov    $0x400604,%ecx # <-- "world"
  40045f:       ba 01 00 00 00          mov    $0x1,%edx
  400464:       be 10 06 40 00          mov    $0x400610,%esi # <-- "%d hello %s! %d"
  400469:       bf 01 00 00 00          mov    $0x1,%edi
  40046e:       31 c0                   xor    %eax,%eax
  400470:       e8 bb ff ff ff          callq  400430 <__printf_chk@plt>
  400475:       31 c0                   xor    %eax,%eax
  400477:       48 83 c4 08             add    $0x8,%rsp
  40047b:       c3                      retq
...

Dumping the read-only data section of the binary shows the printf format string. It looks as if the programmer had directly written the printf line without ever having used pprintpp:

bash $ objdump -s -j .rodata example
...
Contents of section .rodata:
 400600 01000200 776f726c 64000000 00000000  ....world.......
 400610 25642068 656c6c6f 20257321 2025640a  %d hello %s! %d.
 400620 00                                   .

More Examples

In printf, in order to e.g. add a minimum width to a number as in printf("%10d", 123);, one needs to combine format specifiers (10) with conversion letters (d).

In pprintpp, this is easily possible, too: You would just write {10} to get the same effect. The library picks the format specifiers from between the curly braces and merges them with the correct conversion letter that it deduces from the type of the actual expression. This way you get %10d for integers and %10f for floating point variables while using{10} for both. Just look up the format specifiers in the documentation of your printf implementation and use them.

This output is from the file example/main.cpp:

                            Meaning | Format str -> Result
              --------------------- | ---------------------
                       String "abc" | {s}     -> "abc"
           String "abc" + min width | {10s}   -> "       abc"
                  String "abcdefgh" | {.3s}   -> "abc"
               value 0x123, default | {}      -> "291"
                   value 0x123, hex | {x}     -> "123"
                      minimum width | {10}    -> "       291"
                    hex + min width | {10x}   -> "       123"
       hex + min width + hex prefix | {#10x}  -> "     0x123"
  hex + min w. + hex prefix + 0-pad | {#010x} -> "0x00000123"
                                 FP | {}      -> "12.345000"
                     FP + min width | {10}    -> " 12.345679"
         FP + width + max precision | {5.2}   -> "12.35"

Printf Compatibility

pprintpp will transform a tuple (format_str, [type list]) to printf_compatible_format_string.

That means that it can be used with any printf-like function. You just need to define a macro, like for example, these ones for printf and snprintf:

#define pprintf(fmtstr, ...) printf(AUTOFORMAT(fmtstr, ## __VA_ARGS__), ## __VA_ARGS__)
#define psnprintf(outbuf, len, fmtstr, ...) \
    snprintf(outbuf, len, AUTOFORMAT(fmtstr, ## __VA_ARGS__), ## __VA_ARGS__)

Unfortunately, it is not possible to express this detail without macros in C++. However, the rest of the library was designed without macros at all.

Embedded projects that introduce their own logging/tracing functions which accept printf-style format string, will also profit from this library.

FAQ

Why printf, when there is stream style printing in C++?

Yes, stream style printing is type safe, and from a features perspective clearly superior to printf. However, in some projects, C++ is used without streams. This library was designed to help out developers of such projects with some type safety and comfort.

An older version of the library even contained code which rewrote the parts which come from the STL, so it doesn't even depend on the STL. These can be reimplemented, if someone wishes to use this on some hardcore baremetal project where no STL is available. It's just that no one asked for that reimplementation, yet.

I am pretty happy with fmtlib. Why pprintpp?

Those two libs have similar purposes but completely different approaches. pprintpp is just a compile-time frontend that adds type safety and comfort to existing printf-style functions. fmtlib is a complete string formatting and printing library that produces actual code and acts as a complete substitute for printf and the output/printing part of C++ streams.

Is pprintpp faster than fmtlib?

Yes and no: fmtlib has its own (highly optimized and elegant) code for formatting strings which has demonstrably high performance. When you compare the performance of pprintpp and fmtlib, you actually compare the performance of printf and fmtlib, because pprintpp adds no code to your binary.

Depending on the printf implementation you use, you are faster or slower than fmtlib.

Is pprintpp smaller than fmtlib/some other library?

Certainly. No matter how much you use pprintpp, it will never result in actual runtime code. It only fixes your format strings at compile time.

If binary size is your concern and you want to reduce the number of libraries you link against, pprintpp is totally for you.

This library has first been used in a C++ bare metal operating system project that was also optimized for size. For logging purposes it used its own tiny printf implementation combined with pprintpp (it was not linked with the standard libc or runtime parts of the STL). This way the whole printing-related part of the binary was kept within a few hundred bytes.

Will I profit over fmtlib?

fmtlib is a full-blown, highly optimized formatting library. pprintpp is only a preprocessor which enables for automatically composing printf-compatible format strings. With other words: pprintpp is a printf frontend.

You will profit from pprintpp over fmtlib if:

  • You don't need more formatting features than printf and friends provide, and/or cannot use something else than printf for whatever reason.
  • You have your own tiny and/or fast printf implementation and want to make it type safe and comfortable to use
  • You want to add type safety and comfort to your printing without adding additional runtime code.

If you are writing a user space application that can handle some bytes of printing library, then you are most probably better with fmtlib or the standard C++ streams.

I don't see how this helps printing my own types/classes?

You are right. It doesn't. Printing for example a custom vector type with pprintpp will always look like this:

pprintf("My vector: ({}, {}, {})\n", vec[0], vec[1], vec[2]);

Due to the nature of pprintpp just being a format string preprocessor which will call a printf-like function in the end, it will not be possible to print custom types.

If it is not possible to express something with printf directly (printf(" %FOOBARXZY ", my_custom_type_instance);), then pprintpp will not help out here.

However, if you use some kind of my_own_printf implementation, which actually accepts a format specifier like %v for struct my_vector, and is able to pretty print that type at runtime, then it is easy to extend pprintpp with knowledge of this type, yes. Then it is possible to do pprintf("my own vector type: {}\n", my_own_vector);.

Baseline: If you are asking for actual formatting features, you will need to add these features to the formatting function, not to pprintpp.

Isn't that no runtime overhead feature just a result of compiler optimization?

No. The whole format string is preprocessed at compile time, this is guaranteed.

pprintpp's People

Contributors

blitz avatar braph avatar shreyasbharath avatar tfc 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

pprintpp's Issues

Explicit length identifier broken

Hi,

I just found this issue today.

Consider the following code:

uint64_t val = 0x8ff00000000;
pprintf("{#08x}\n", val);

Expected output:

0x00000000

Actual output:

0x8ff00000000

Add support for string view?

Would it be possible to add support for printing non-null terminated string views using the precision spec, e.g.

 std::string_view s = "world";
 pprintf("hello {}!\n", s);

would generate

printf("%.*s\n", s.size(), s.data());

Mixing format syles causes wrong output

If you mix classic %[] formats with {} the output if broken. In some cases you get double 0x, other following parameters are cased to the wrong size.

pprintf("test %s {#x} {} {x}", c_string, uint64_t, void_ptr, int)

pprintpp needs extra cstdio include

pprintf() expands to a call to printf() yet it doesn't include cstdio. This leads to the somewhat confusing error seen below:

src/main.cpp: In function ‘int main()’:
src/main.cpp:65:3: error: ‘printf’ was not declared in this scope
   65 |   pprintf("Hello World!\n");
      |   ^~~~~~~
user-virtio-net/src/main.cpp:8:1: note: ‘printf’ is defined in header ‘<cstdio>’; did you forget to ‘#include <cstdio>’?

Minimal example of the problem:

#include <pprintpp/pprintpp.hpp>

int main()
{
  pprintf("Hello World!\n");
  return 0;
}

For me the expected behavior would be for this to work without manually including other headers.

Header soup

Other projects include system headers as needed, as well as setting locale as
needed, leading to clean code:

#include <fmt/format.h>
int main() {
   fmt::print("{:n}\n", 1000);
}

pprintpp does not include system headers as needed, and does not set locale as
needed, leading to verbose code:

#include "pprintpp.hpp"
#include <clocale>
#include <cstdio>
int main() {
   setlocale(LC_ALL, "");
   pprintf("{'}\n", 1000);
}

C++11 Lambda expression instead of local class/struct in AUTOFORMAT

Hey,
Will trying to use pprintpp with MSVC, CLANG and Compilers for Microcontrollers (Aurix TriCore) I had some issues with the AUTOFORMAT Macro - not every Compiler seems to support the "local class" scheme used with the ({struct....}) statement.

Hence, I modified the AUTOFORMAT Macro to use a lambda expression which gets called - I am not aware if this will cause performance-issues, so maybe you can give me additional advice?

This approach is compatible with at least all the compilers I have tried (especially MSVC (Visual Studio Build Tools 2019) caused problems).

Here my modification:

#define AUTOFORMAT(s, ...) \
        []() {                                                                              \
            struct strprov                                                                  \
            {                                                                               \
                static constexpr const char *str() { return static_cast<const char *>(s); } \
            };                                                                              \
            using paramtypes = decltype(pprintpp::tie_types(__VA_ARGS__));                  \
            using af = pprintpp::autoformat_t<strprov, paramtypes>;                         \
            return af::str();                                                               \
        }()


#define pprintf(s, ...) printf(AUTOFORMAT(s, ## __VA_ARGS__), ## __VA_ARGS__);

}

If you are interested, I could do a fork and make a pull request.

Kind regards

Explicit hexadecimal format with signed argument

Current implementation, silently replaces hexadecimal specifier with decimal one in the case of signed argument. Eg, pprintfpp("{x}", 1) translates to printf("%d", 1) which is somewhat unexpected. In my opinion, it is better to have the "%x", as it doesn't break anything and does what the user asked for.

tie_types ... no implementation ?

using MSVC (the latest, all up to date), does not compile.
pprintpp.hpp , arround line 166:

       template <typename ... Ts>
            make_t<Ts...> tie_types(Ts...);

it seems MSVC can not compile that ...

ps: using clang all works

Thank you!

I just wanted to say, thank you for this awesome library ❤️ . This is such an awesome addition for embedded projects where the cost of a printf bug is high and we've been caught out by it many times.

I was thinking of implementing something like this myself but then I came across this after some searching. Once we start using it a lot, you'll probably see PRs coming through from us :)

reorder printf paramters

As you wrote in the FAQ, printfwill not reorder arguments as in the example of fmt: fmt::format("{0}{1}{0}", "abra", "cad"); but wouldn't it be possible to create a wrapper function that looks up a translation from the format string and rearranges the parameters before calling printf? This would allow use to create binaries that contains only the literals for a specific language and still supports a usage like gettext.

Example:
printtext( "{1} beer to {0} guests.", 4, 2 );

could lookup the translation of "{1} beer to {0} guests." to the target language, check the arguments and then generate a function that rearranges the arguments when calling printf.

https://github.com/dbj-systems/pprintpp_msvc

Fixed it on the level of the AUTOFORMAT macro, by using lambda inside a macro. Compiles, runs, tests ok. But.

MSVC breaks under (what it thinks are) too complex recursive templates of the 'TYPE LIST' used. See the one example in the main().

CLANG + VS2019 -- no issues whatsoever ...

AVOID_STL requires STL includes

When compiling with -DPPRINTPP_AVOID_STL the file stl_symbols.hpp still requires cstddef. I'd suggest to include stddef.h in that case.

How to integrate with user defined types

I wonder, if it would be possible to add support for user defined types and if so, how. Or if not, where would one add support for user defined types. Ideas?

struct foo {
} my_foo;

std::size_t print( char* buffer, std::size_t buffer_size, const foo& );
pprintpp( "This is my foo: {}", my_foo );

Could we somehow extend pprintpp to have print() invoked with some statically allocated buffer for formatting purpose?

I know, pprintpp() is "just" creating printf style format strings, but in real world applications, user defined types have to be added sooner or later.

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.