How the Compile API in fmtlib Performs Compile-Time Format String Parsing
The fmt::compile API eliminates runtime format string overhead by parsing literals at compile time through a constexpr recursive state machine that builds type-safe formatting objects.
The {fmt} library (fmtlib/fmt) provides a compile-time formatting API implemented in include/fmt/compile.h that transforms format strings into optimized formatting structures during compilation. Unlike standard runtime formatting, this approach resolves argument types, specifiers, and field positions entirely at compile time, generating machine code that writes output directly without parsing strings at runtime.
The Compile-Time Parsing Workflow
The compile API follows a strict pipeline that converts string literals into executable formatting objects through template metaprogramming and constexpr evaluation.
Marking Literals for Compilation
The process begins with the FMT_COMPILE(s) macro defined in include/fmt/compile.h (lines 37‑40). This macro wraps a string literal and converts it into a compiled_string type when the compiler supports C++17 if constexpr and return-type deduction. If these features are unavailable, it falls back to standard FMT_STRING behavior.
When you invoke fmt::format(FMT_COMPILE("value: {}"), 42), the compiler selects an overload of fmt::format that accepts compiled_string (lines 54‑59). This overload forwards the literal to detail::compile<T...>(S{}), where S represents the compiled literal type and T... captures the argument type list.
The constexpr Recursive Parser
Inside include/fmt/compile.h (lines 61‑69), detail::compile<T...>(S{}) creates a basic_string_view of the literal and invokes detail::compile_format_string<type_list<T...>, 0, 0, ...>(fmt). This function operates as a constexpr recursive state machine that walks the format string character by character at compile time.
The parser distinguishes three cases for each character (lines 82‑95):
- Opening brace
{: Initiates replacement field parsing - Closing brace
}: Handles escaped braces or syntax errors - Ordinary text: Accumulates literal characters via
parse_text
For ordinary text sequences, parse_text locates the next brace and creates either a text node (multiple characters) or a code_unit node (single character), returning the remainder of the string for further processing.
Parsing Replacement Fields
When encountering an opening brace, the parser determines the field type through parse_replacement_field_then_tail (lines 100‑138). It handles:
- Escaped braces:
{{and}}become static text nodes - Positional fields:
{}or{:}use automatic argument indexing - Indexed fields:
{N}specifies the Nth argument explicitly - Named fields:
{name}resolves to named arguments (C++20)
The helper parse_arg_id extracts argument identifiers, while format specifiers following the colon (e.g., {:04x}) trigger parse_specs to create a spec_field rather than a plain field.
Type Resolution and Tree Construction
The template get_type<N, Args> (lines 15‑22) retrieves the compile-time type of the Nth argument from the accumulated type_list<Args...>. This enables the parser to instantiate strongly-typed field or spec_field structures that match the argument types exactly, ensuring format specifiers are validated against the actual types during compilation.
The parser composes these nodes into a nested concat tree (lines 44‑52) that represents the entire format string. Each node implements a format(OutputIt, ...) method, creating a linked structure where each element knows precisely how to output its segment.
Core Components of the Compile API
compiled_string and FMT_COMPILE
The compiled_string class (line 20) serves as a marker type indicating that a string literal should undergo compile-time parsing. The FMT_COMPILE macro forces literals through this compile path, bypassing the runtime parsing used by standard fmt::format calls.
Field Representations
The compile API generates distinct node types for different format elements:
text: Static character sequences extracted viaparse_textcode_unit: Single-character literalsfield: Simple replacement fields without specifiersspec_field: Fields with compile-time parsed format specificationsruntime_named_field: Named argument placeholders requiring runtime resolution
The concat Structure
The concat template (lines 44‑52) implements a compile-time linked list that stitches formatting nodes together. Because all members are constexpr, the entire tree evaluates to constant expressions, allowing the compiler to optimize the formatting logic into inline output operations.
Practical Usage Examples
Basic Compile-Time Formatting
#include <fmt/compile.h>
int main() {
// Parsed entirely at compile time
std::string s = fmt::format(FMT_COMPILE("The answer is {}"), 42);
// Generates: "The answer is 42"
}
In this example, FMT_COMPILE creates a compiled_string type. The parser builds a field<char, int, 0> node for the {} placeholder, resolving the integer type at compile time. The resulting code writes the value directly without runtime format-string scanning.
Format Specifiers at Compile Time
#include <fmt/compile.h>
int main() {
std::string s = fmt::format(FMT_COMPILE("{:04x}"), 255);
// Generates: "00ff"
}
Here, compile_format_string encounters the : delimiter and invokes parse_specs to create a spec_field<char, int, 0>. This node stores a formatter<int, char> configured with the 04x specification at compile time, generating code that performs hexadecimal conversion and zero-padding without runtime overhead.
Named Arguments with C++20
#include <fmt/compile.h>
using namespace fmt::literals;
int main() {
auto fmt_str = "{val}"_cf; // Compile-time literal operator
std::string s = fmt::format(fmt_str, fmt::arg("val", 123));
}
When FMT_USE_NONTYPE_TEMPLATE_ARGS is enabled, the _cf literal operator forwards to FMT_COMPILE. The parser treats {val} as a named field, creating either a compile-time resolved field or a runtime_named_field that fetches the argument by name during execution while maintaining type safety.
Summary
FMT_COMPILEconverts string literals intocompiled_stringtypes that trigger compile-time parsing ininclude/fmt/compile.h.detail::compile_format_stringimplements a constexpr recursive parser (lines 82‑91) that walks format strings and builds node trees at compile time.- Node types (
field,spec_field,text,concat) represent parsed format elements as lightweight templates with zero runtime overhead. - Type resolution occurs via
get_typetemplate metaprogramming (lines 15‑22), ensuring argument types match format specifiers during compilation. - Execution happens through
fmt::format(const CompiledFormat&, ...)(lines 76‑84), which writes output using pre-generated formatting logic without runtime string analysis.
Frequently Asked Questions
What is the performance benefit of using fmt::compile?
fmt::compile eliminates runtime format string parsing entirely. Because detail::compile_format_string executes at compile time, the resulting binary contains pre-computed formatting instructions rather than parsing loops. This reduces executable size for repeated format patterns and removes branching logic from hot paths, often resulting in formatting speeds comparable to handwritten code.
Does fmt::compile work with runtime format strings?
No, the compile API requires string literals known at compile time. The FMT_COMPILE macro and compiled_string type depend on template metaprogramming and constexpr evaluation to parse the format string. For runtime strings, use the standard fmt::format API, which performs runtime parsing using the same underlying machinery in include/fmt/format.h.
How does compile-time type safety work in the compile API?
Type safety is enforced through template argument lists and get_type resolution. When you call fmt::format with a compiled_string, the compiler deduces the argument types into a type_list<T...>. The get_type<N, Args> template (lines 15‑22) retrieves the Nth type at compile time, ensuring that field and spec_field nodes are instantiated only with compatible format specifications, catching type mismatches during compilation rather than at runtime.
What C++ standard is required for fmt::compile?
Full functionality requires C++17 or later. The implementation relies on if constexpr for compile-time branching and improved return-type deduction to enable FMT_COMPILE expansion (lines 37‑40). While basic formatting works on older standards, the compile-time parsing and compiled_string optimizations require constexpr capabilities introduced in C++17, with additional features like non-type template parameter string literals available in C++20.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →