What Is the fmtlib C API Wrapper? Complete Guide to Safe C Formatting

The fmtlib C API wrapper provides a pure-C interface defined in include/fmt/fmt-c.h and implemented in src/fmt-c.cc that exposes the {fmt} library's type-safe formatting capabilities to C programs through a lightweight argument translation layer.

The {fmt} library delivers fast, modern C++ formatting, but the C API wrapper extends these benefits to pure C projects without requiring C++ compilation units. By providing a bridge between C data types and the library's C++ core engine, this wrapper enables safe, high-performance string formatting in embedded systems, language bindings, and legacy C codebases.

Core Architecture of the C API Wrapper

The fmt_arg Structure and Type Enumeration

The foundation of the C API wrapper is the fmt_arg struct, a discriminated union that safely transports C values into the C++ formatting engine. This struct pairs a fmt_type enum tag with a union capable of storing integers, floating-point values, booleans, characters, C-strings, and pointers.

The fmt_type enum enumerates all supported argument categories, allowing the implementation in src/fmt-c.cc to dispatch each value to the correct C++ template instantiation.

Type-Safe Construction Macros

Manual construction of fmt_arg instances is error-prone, so the wrapper provides convenience helpers: fmt_from_int, fmt_from_uint, fmt_from_double, fmt_from_bool, fmt_from_char, fmt_from_str, and fmt_from_ptr.

The FMT_MAKE_ARG(x) macro leverages C11 _Generic selection to automatically choose the appropriate helper, eliminating manual type specification. For multiple arguments, FMT_MAKE_ARGLIST(...) expands variadic parameters into a stack-allocated array of fmt_arg structures.

Essential Functions for C Formatting

Buffer Formatting with fmt_vformat

The fmt_vformat function writes formatted output to a user-supplied buffer with explicit bounds checking. Its signature is:

int fmt_vformat(char *buf, size_t size, const char *fmt, const fmt_arg *args, size_t num_args);

This function returns the number of characters written on success, or a negative error code such as fmt_error_invalid_arg on failure. The implementation in src/fmt-c.cc converts the args array into fmt::basic_format_arg<fmt::format_context> objects before forwarding to fmt::vformat_to_n.

Direct Stream Output with fmt_vprint

For immediate output without intermediate buffers, fmt_vprint writes directly to a FILE* stream:

int fmt_vprint(FILE *stream, const char *fmt, const fmt_arg *args, size_t num_args);

This function is ideal for logging and console output, utilizing fmt::vprint internally while presenting a pure C interface.

Convenience Macros for Direct Usage

The wrapper exposes fmt_format and fmt_print as variadic macros that wrap the v variants. These accept standard C variadic arguments and internally expand them using FMT_MAKE_ARGLIST(...), enabling intuitive usage:

char buf[256];
fmt_format(buf, sizeof(buf), "User: %s, ID: %d", "Alice", 42);
fmt_print(stdout, "Hex: %#x, Bool: %s\n", 0xDEAD, true);

Implementation and Safety Details

Bridging C and C++ Type Systems

In src/fmt-c.cc, the wrapper translates each fmt_arg into a fmt::basic_format_arg<fmt::format_context> that the C++ engine understands. This translation layer maintains zero-overhead abstraction while ensuring C programs cannot trigger C++ exceptions.

The implementation forwards processed calls to fmt::vformat_to_n for buffer operations or fmt::vprint for stream output, ensuring C programs benefit from the library's optimized formatting algorithms.

Argument Limits and Error Handling

To guarantee predictable stack usage in C environments, the wrapper enforces a configurable limit of 16 arguments (defined by max_c_format_args). Exceeding this limit returns fmt_error_invalid_arg rather than causing undefined behavior.

Error handling adheres to C conventions: functions return negative integer constants (fmt_error, fmt_error_invalid_arg) rather than throwing exceptions, making the API suitable for embedded and systems programming contexts.

Practical Code Examples

Formatting into a Buffer

#include <stdio.h>
#include "fmt/fmt-c.h"

int main(void) {
    char buf[128];
    int rc = fmt_format(buf, sizeof(buf),
                       "Name: %s, Age: %d, Score: %.2f",
                       "Alice", 30, 95.5);
    if (rc >= 0)
        puts(buf);          // prints: Name: Alice, Age: 30, Score: 95.50
    else
        fprintf(stderr, "Formatting error %d\n", rc);
    return 0;
}

Direct Printing to stdout

#include "fmt/fmt-c.h"

int main(void) {
    /* fmt_print writes directly to the supplied FILE* */
    fmt_print(stdout, "Hex: %#x, Bool: %s\n", 0xDEAD, true);
    /* Output: Hex: 0xdead, Bool: true */
    return 0;
}

Using Automatic Type Selection

#include "fmt/fmt-c.h"

void log_error(const char *msg, int code) {
    fmt_print(stderr, "ERROR: %s (code %d)\n", msg, code);
}

/* The macro automatically creates the fmt_arg array */
int main(void) {
    log_error("File not found", -1);
    return 0;
}

Summary

  • The C API wrapper consists of the header include/fmt/fmt-c.h and implementation src/fmt-c.cc, providing pure-C access to {fmt} capabilities.
  • The fmt_arg struct and fmt_type enum create a type-safe bridge between C values and the C++ formatting engine.
  • FMT_MAKE_ARG leverages C11 _Generic for automatic, compile-time type selection without runtime overhead.
  • Core functions fmt_vformat and fmt_vprint offer buffer and stream-based output with consistent C-style error handling.
  • A 16-argument limit ensures safe stack usage, with negative error codes returned for failure conditions.

Frequently Asked Questions

How does the fmtlib C API wrapper differ from standard printf?

Unlike printf, the C API wrapper enforces type safety through the fmt_arg structure and FMT_MAKE_ARG macro, eliminating format string vulnerabilities. It supports advanced formatting features from the C++ library—including positional arguments and custom format specifiers—while maintaining a pure C interface compatible with C99 and later standards.

What is the maximum number of arguments supported by the C API?

The wrapper supports up to 16 arguments by default, configurable via the max_c_format_args constant. This limit ensures predictable stack allocation in resource-constrained environments, returning fmt_error_invalid_arg if exceeded rather than causing buffer overflows.

Can I use the C API wrapper in C++ projects?

While technically possible, C++ projects should use the native fmt::format and fmt::print APIs directly rather than the C API wrapper. The C++ interface provides superior compile-time type checking and performance by avoiding the translation layer implemented in src/fmt-c.cc.

Where are the C API wrapper source files located?

The public interface is defined in include/fmt/fmt-c.h, while the implementation that converts C arguments to C++ format contexts resides in src/fmt-c.cc. Both files are maintained in the fmtlib/fmt repository and compiled as part of the standard library build.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →