How fmtlib Implements Compile-Time Format String Parsing with compile.h
fmtlib achieves compile-time format string parsing through constexpr utilities, template metaprogramming, and a recursive parser that converts format strings into compile-time AST nodes defined in include/fmt/compile.h.
The fmt library eliminates runtime formatting overhead by processing format strings at compile time using modern C++ features. The header include/fmt/compile.h provides the machinery to transform string literals into type-safe, pre-parsed objects that encode formatting logic directly into the type system.
Overview of the Compile-Time Parsing Architecture
The implementation relies on marking string literals as compiled strings, then recursively parsing them at compile time to build an abstract syntax tree (AST) represented as C++ types. Each node in this tree—from text fragments to replacement fields—carries its own constexpr format method that writes directly to output iterators.
When you invoke fmt::format with a compiled string, the library bypasses the runtime parser entirely. Instead, it instantiates the pre-built type hierarchy, executing only the format methods of the generated AST nodes.
Triggering Compile-Time Parsing with FMT_COMPILE
The primary entry point is the FMT_COMPILE(s) macro defined at line 37 of include/fmt/compile.h. When the compiler supports if constexpr and return type deduction, this macro expands to FMT_STRING_IMPL(s, fmt::compiled_string), effectively tagging the literal for compile-time processing.
// This macro marks the string for compile-time parsing
std::string s = fmt::format(FMT_COMPILE("Result: {}"), 42);
This expansion ensures that the format string undergoes parsing during compilation rather than at runtime.
Marking and Detecting Compiled Strings
To distinguish compiled strings from runtime strings, the library uses a marker class and type trait defined at lines 20-24:
class compiled_string {};
template<class S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
The compiled_string class serves as an empty base class, while is_compiled_string leverages std::is_base_of to detect the special type. When fmt::format receives a first argument satisfying is_compiled_string, it immediately routes the call to the compile-time code path.
The Recursive Constexpr Parser
Once a compiled string is detected, detail::compile<T...>(S{}) (lines 55-67) instantiates a basic_string_view of the literal and delegates to detail::compile_format_string<type_list<Args...>, 0, 0, …>(fmt) (lines 64-66).
Parsing Format String Fragments
The function compile_format_string (lines 382-452) walks the string character by character using constexpr recursion. It distinguishes between escaped braces, indexed arguments, named arguments, and format specifiers, delegating to specialized helpers such as:
parse_arg_idparse_specsparse_replacement_field_then_tail
Each fragment of the format string becomes a specific compile-time type:
detail::text(lines 27-35): Encapsulates plain text segmentsdetail::code_unit(lines 46-54): Represents single character unitsdetail::field(lines 71-89): Handles simple replacement fieldsdetail::spec_field(lines 126-138): Manages fields with format specifiers (width, precision, type)detail::runtime_named_field(lines 94-121): Supports named runtime arguments
Each struct is marked with is_compiled_format and provides a constexpr format(OutputIt, const T&…) method that writes directly to the output iterator.
Building the Compile-Time AST
The parser stitches these fragments together using detail::concat (lines 44-53), a template struct that chains left-hand and right-hand nodes. Each concat instance provides a format method that forwards to its left side, then its right side. The result of detail::compile is a nested type hierarchy representing the entire format string as a compile-time executable structure.
Executing Compiled Formats
The overload fmt::format(const CompiledFormat&, const T&… args) (lines 76-85) instantiates a std::basic_string and invokes cf.format(std::back_inserter(s), args…). Because all formatting work is encoded in the compile-time type hierarchy, no runtime parsing occurs—only the format methods of the generated AST nodes are executed.
// Zero runtime parsing overhead
auto compiled = FMT_COMPILE("Value: {:.2f}");
std::string output = fmt::format(compiled, 3.14159);
C++20 User-Defined Literal Support
For C++20 compilers supporting non-type template arguments, fmt provides operator""_cf (lines 55-60). This user-defined literal allows compile-time parsing without macros:
using namespace fmt::literals;
// Equivalent to FMT_COMPILE("{}")
auto fmt = "{}"_cf;
std::string s = fmt::format(fmt, 3.14);
Both approaches generate identical compile-time ASTs, ensuring consistent zero-overhead formatting across different C++ standards.
Summary
FMT_COMPILE(s)triggers compile-time parsing by marking strings with thecompiled_stringbase class.is_compiled_stringdetects marked strings and routes them away from runtime parsing.compile_format_stringrecursively walks the literal at compile time to identify text, fields, and specifiers.- AST nodes (
text,field,spec_field, etc.) encode formatting logic as constexpr types withformatmethods. detail::concatbuilds the final type hierarchy representing the complete format string.- Execution involves only calling the pre-compiled
formatmethods, eliminating all runtime string parsing.
Frequently Asked Questions
What is the performance benefit of compile-time format string parsing?
Compile-time parsing eliminates the overhead of analyzing format strings at runtime. According to the fmtlib source code, when using FMT_COMPILE, the library skips the runtime parser entirely, executing only the format methods of pre-generated AST nodes. This reduces binary size for repeated formatting operations and improves execution speed by removing string traversal and state machine logic from the hot path.
Do I need C++20 to use compile-time formatting?
No. The FMT_COMPILE macro works with C++14 and later by leveraging constexpr functions and template metaprogramming. However, C++20 provides additional convenience through the _cf user-defined literal, which requires support for class types as non-type template arguments. Both approaches generate equivalent compile-time representations.
How does fmtlib handle format specifiers at compile time?
When compile_format_string encounters a colon within braces, it invokes parse_specs to analyze width, precision, alignment, and type specifiers. These specifiers become template parameters within detail::spec_field (lines 126-138), encoding the formatting requirements directly into the type system. The resulting struct stores these specifications as compile-time constants, allowing the format method to apply them without runtime interpretation.
Can runtime format strings benefit from compile.h?
No. The compile.h infrastructure specifically targets compile-time known strings marked with compiled_string. Runtime strings follow the standard runtime parsing path in include/fmt/format.h. Attempting to use runtime strings with the compile-time API will result in compilation errors, as the template machinery requires the format string content to be visible during compilation.
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 →