A C++11 compile-time printf library
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
benchmark
include/prima
test
.clang-format
Jamroot
README.md

README.md

prima

Prima is an implementation of the C I/O functions that parses the format string at compile time using boost::metaparse, and then generates input/output formatting code at compile time instead of runtime. This results in faster and safer (type-checked) I/O. Prima supports multiple backends that do the actual I/O, making Prima a flexible C format string compiler.

This library is a work in-progress.

Dependencies

Prima currently requires Boost 1.61.

Supported Formatting

Input

Not yet-implemented. Will support boost::spirit::qi and default to boost::spirit::x3.

Output

Prima output I/O matches the behavior of the standard C behavior. Some flags are compile-time errors if they have no effect or are conflicting (i.e. ' ' and '+' is a compile-time error). If a flag is not supported for a type, it is a compile-time error if used.

Format Flag Valid Argument Types Supported Flags
   d, i      |         Any built-in signed integer type (char, short, int, etc.)              | Width, Precision, '-', '0', ' ', and '+'.

e, E, f, F, g, G | float, and double | Width, Precision, '-', '0', ' ', '+', and '#' u | Any unsigned integer type (unsigned char, unsigned short, unsigned int, etc.) | Width, Precision, '-', and '0'. o, x, X | Any unsigned integer type (unsigned char, unsigned short, unsigned int, etc.) | Width, Precision, '-', and '0'. s | const char*, std::string | Width, Precision, and '-'

  • The length modifiers ('hh', 'll', etc.) are not supported, because variadic templates are used instead of the type opaque C variadic arguments (so they are not needed).
  • Pointers can be output by converting to std::uintptr_t and using any of the format flags that support an unsigned integer type ("%p" is not needed).
  • A zero length width is a compile-time error.
  • The '0' flag with no specified width is a compile-time error.
  • The combination of flags '+' and ' ' is a compile-time error.
  • The combination of flags '0' and '-' is a compile-time error.
  • The combination of the '0' flag and usage of the precision field with an integer format flag (d, i, u, o, x, X) is a compile-time error.

Usage

The prima I/O functions require a variadic compile-time string as input. A macro PRIMA_FMT can convert a string literal to the compile-time string in a C++11 compatible compiler. The user-literal prima::literals::_fmt can be used in compilers that support literal operator templates for strings:

{
    prima::printf(PRIMA_FMT("%s"), "world");
}
{
    using prima::literals;
    prima::printf("hello %s"_fmt, "world");
}

The user string literal _fmt is the preferred method, because it will have more efficient compile-times.

Output

printf

Behaves just like std::printf:

prima::printf(PRIMA_FMT("hello %s"), "world");
sprintf

prima::sprintf differs slightly from std::sprintf; the first argument is templated and must meet output iterator requirements, and the output is never null-terminated. Since char* is a valid output iterator, prima::sprintf can be used in place of std::sprintf or other scenarios:

{
    char buffer[100] = {0};
    // Never checks for buffer size. Does not null-terminate.
    prima::sprintf(std::begin(buffer), PRIMA_FMT("hello %s"), "world");
}
{
    std::string buffer{};
    // Throws if std::bad_alloc. Implicitly null-terminated due to std::string.
    prima::sprintf(std::back_inserter(buffer), PRIMA_FMT("hello %s"), "world");
}

prima::sprintf will also update the output iterator to one-past the last write location, iff the argument is passed as a mutable l-value.

snprintf

prima::snprintf differs slightly from std::snprintf; the first argument is templated and must meet output iterator requirements, and the function does not stop until size bytes have been output. Since char* is a valid output iterator, prima::sprintf can be used in place of std::sprintf or other scenarios:

{
    char buffer[100] = {0};
    // Does not null-terminate. Stops after sizeof(buffer) bytes (not sizeof(buffer) - 1).
    prima::snprintf(std::begin(buffer), PRIMA_FMT("hello %s"), sizeof(buffer), "world");
}
{
    std::string buffer{};
    buffer.resize(100);
    // Implicitly null-terminated due to std::string. Stops after buffer.size() bytes.
    prima::snprintf(&buffer[0], PRIMA_FMT("hello %s"), buffer.size(), "world");
}

prima::snprintf will also update the output iterator to one-past the last write location, iff the argument is passed as a mutable l-value.

Backends

Input

Not yet-implemented. Will support boost::spirit::qi and default to boost::spirit::x3.

Output

Karma

boost::spirit::karma is the only backend for output, so it is the current default. Explicit usage example:

using prima::literals;
prima::printf.call<prima::backend::karma>()("hello %s"_fmt, "world");

Performance Benchmarks

OSX (libc++) - Clang 3.5 - Intel i5-3210M (2.5 GHz 3MB Cache)

Running "%f" with input -100.000000 benchmark 1000000 times for each implementation...
        std::sprintf :0.231175 microseconds per run {checksum: 3dfd240}
       std::snprintf :0.230521 microseconds per run {checksum: 3dfd240}
      prima::sprintf :0.082181 microseconds per run {checksum: 3dfd240}
     prima::snprintf :0.080238 microseconds per run {checksum: 3dfd240}
Running "%f" with input 0.000000 benchmark 1000000 times for each implementation...
        std::sprintf :0.174141 microseconds per run {checksum: 40d9900}
       std::snprintf :0.171285 microseconds per run {checksum: 40d9900}
      prima::sprintf :0.062569 microseconds per run {checksum: 40d9900}
     prima::snprintf :0.068810 microseconds per run {checksum: 40d9900}
Running "%f" with input 100.000000 benchmark 1000000 times for each implementation...
        std::sprintf :0.227993 microseconds per run {checksum: 41cdb40}
       std::snprintf :0.226350 microseconds per run {checksum: 41cdb40}
      prima::sprintf :0.072780 microseconds per run {checksum: 41cdb40}
     prima::snprintf :0.072128 microseconds per run {checksum: 41cdb40}
Running "%f" with input 1234.123413 benchmark 1000000 times for each implementation...
        std::sprintf :0.257176 microseconds per run {checksum: 41cdb40}
       std::snprintf :0.256577 microseconds per run {checksum: 41cdb40}
      prima::sprintf :0.096077 microseconds per run {checksum: 41cdb40}
     prima::snprintf :0.095936 microseconds per run {checksum: 41cdb40}

Benchmark averages:
        std::sprintf :0.222621 microseconds per run {checksum: 102721c0}
       std::snprintf :0.221183 microseconds per run {checksum: 102721c0}
      prima::sprintf :0.078402 microseconds per run {checksum: 102721c0}
     prima::snprintf :0.079278 microseconds per run {checksum: 102721c0}

Note: This is from latest master branch, but not C++11 branch