ctprintf is a small C++20, header-only formatter for embedded and
freestanding-oriented applications. It provides familiar printf-style
formatting, validates literal format strings and argument types at compile
time, and writes one character at a time to an application-provided output.
The library is intended for diagnostic output such as UART, ITM/SWO, log
buffers, or host-side test buffers. It does not use printf, iostreams,
std::string, or dynamic allocation itself.
- C++20 header-only library
- Compile-time validation of literal format strings and argument types
- No dynamic allocation in the formatter
- No dependency on libc formatting functions
- Pluggable output through a single
put(char)operation - Integer, character, string, pointer, width, flag, and escaped-percent formatting
#include <ctprintf/format.hpp>
struct Uart {
void put(char character)
{
// Transmit character.
}
};
int main()
{
Uart uart;
ctprintf::format(
uart,
"PC=%08x LR=%08x\n",
0x08001234U,
0x08005678U);
}The output is:
PC=08001234 LR=08005678
The first argument to ctprintf::format can be any type that provides:
void put(char character);For example, a fixed-size buffer can be used in a test or a logging adapter:
struct BufferOutput {
char *buffer;
std::size_t position = 0;
void put(char character)
{
buffer[position++] = character;
}
};ctprintf does not own the output or perform bounds checking; the output type
is responsible for transport, storage, synchronization, and capacity handling.
Format strings must be string literals. They are validated during compilation: the number of conversions must match the number of arguments, and each argument must have a supported type for its conversion.
ctprintf::format(output, "value=%08x\n", 42U); // Valid.
ctprintf::format(output, "value=%08x\n", "42"); // Compile-time error.The following conversions are supported:
| Conversion | Accepted argument | Description |
|---|---|---|
%d, %i |
Signed integral type | Signed decimal |
%u |
Unsigned integral type, excluding bool |
Unsigned decimal |
%o |
Unsigned integral type, excluding bool |
Octal |
%x |
Unsigned integral type, excluding bool |
Lowercase hexadecimal |
%X |
Unsigned integral type, excluding bool |
Uppercase hexadecimal |
%c |
Integral type | Character |
%s |
Type convertible to const char * |
Null-terminated string |
%p |
Object pointer, void pointer, or nullptr |
Pointer in hexadecimal |
%% |
No argument | Literal percent sign |
%s formats a null pointer as (null). %p always includes a 0x prefix;
for example, nullptr is formatted as 0x0.
The formatter supports the following flags and a decimal minimum field width:
| Option | Meaning |
|---|---|
- |
Left-align within the field width |
+ |
Prefix non-negative signed decimal values with + |
| space | Prefix non-negative signed decimal values with a space |
# |
Add an octal or hexadecimal prefix where applicable |
0 |
Pad numeric values with zeroes when not left-aligned |
| width | Minimum field width, for example %08x or %-6s |
Precision, length modifiers, floating-point conversions, positional arguments, and runtime-provided format strings are not supported.
Requirements:
- A C++20-capable compiler
- CMake 3.25 or newer
- GoogleTest, when building the test suite
Configure and build the library:
cmake -S ctprintf -B build
cmake --build buildTo build and run the tests, enable them explicitly:
cmake -S ctprintf -B build -DENABLE_TESTING=ON
cmake --build build
ctest --test-dir build --output-on-failureThe optional benchmarks compare ctprintf with std::snprintf using the same
printf-style formats and fixed-size output buffers. They require Google
Benchmark:
cmake --workflow --preset benchmark-workflow
./ctprintf/build/benchmark/benchmarks/ctprintf_benchmarksThe benchmark target is separate from CTest because execution time varies between machines and system load.
The benchmark dashboard tracks performance
over time. Results are updated on pushes to main and weekly. To publish the
dashboard, enable GitHub Pages for the repository with the gh-pages branch as
the source.
CPack creates a gzip-compressed tarball containing the installable headers, CMake package files, README, and license:
cpack --config build/CPackConfig.cmakeThe archive is written to the build directory and is named
ctprintf-<version>-<system>.tar.gz, for example
ctprintf-0.1.0-Linux.tar.gz.
Install the header and CMake package files with:
cmake --install build --prefix /desired/prefixAn application can then consume the installed package:
find_package(ctprintf CONFIG REQUIRED)
target_link_libraries(my_application PRIVATE ctprintf::ctprintf)The exported target supplies the include directory and requires C++20.
MIT License. See LICENSE.