What Does the FMT_HEADER_ONLY Macro Do and When Should You Use It?
The FMT_HEADER_ONLY macro switches the {fmt} library into header-only mode, inlining all implementation code directly into your translation units and eliminating the need to link against a compiled library.
The {fmt} library typically builds as a separate compiled target that you link into your application. According to the fmtlib/fmt source code, the FMT_HEADER_ONLY preprocessor macro provides a zero-linkage alternative that moves all implementation code directly into the headers through conditional compilation guards. Understanding when to enable this macro helps you balance build simplicity against compilation overhead in modern C++ projects.
What Is the FMT_HEADER_ONLY Macro?
FMT_HEADER_ONLY is a preprocessor definition that disables external symbol export for the {fmt} library. When this macro is defined before including any {fmt} headers, the library stops exporting compiled symbols and instead makes all template instantiations, formatting functions, and utility implementations available directly from the header files.
In the source repository, this behavior is orchestrated through #ifndef FMT_HEADER_ONLY guards found in include/fmt/format.h, include/fmt/base.h, and include/fmt/fmt-c.h. For example, in include/fmt/format.h (approximately lines 1351–1360), blocks of code responsible for exporting symbols to the compiled library are wrapped in these guards, ensuring they are only active when the macro is not defined.
The CMake build system explicitly defines this macro in CMakeLists.txt (around line 420) through the command target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1). This creates the fmt-header-only target, which automatically injects the definition into any target that links against it.
Header-Only Mode vs. Compiled Library Mode
Understanding the distinction between these two modes helps you choose the right integration strategy for your project.
Compiled Library Mode (Default)
When FMT_HEADER_ONLY is undefined, the library compiles as a separate binary target (fmt). The headers contain only declarations, and the actual implementation code resides in compiled object files. This approach minimizes compile times for projects that include {fmt} headers in multiple translation units and produces smaller individual object files.
Header-Only Mode
When FMT_HEADER_ONLY is defined, the guarded sections in headers like include/fmt/base.h and include/fmt/format.h expose the full implementations. Every translation unit that includes these headers compiles the {fmt} code locally, resulting in a standalone binary with no external library dependency. This mode eliminates the linking step but increases compilation overhead.
When to Use FMT_HEADER_ONLY
Enable the FMT_HEADER_ONLY macro in the following scenarios:
- Simple scripts and single-file projects: When you want to use {fmt} without setting up a build system to compile and link the library.
- Embedded environments: On toolchains that cannot easily produce or link static libraries, or where distributing a single set of headers is preferable to managing binaries.
- Rapid prototyping: When you need immediate functionality without configuring CMake, pkg-config, or linker flags.
- Header-only library constraints: When integrating {fmt} into another header-only library where introducing a compiled dependency would violate the header-only contract.
Performance and Build Considerations Using header-only mode typically increases compile times because the compiler must process the full {fmt} implementation in every translation unit that includes the headers. This can also lead to slightly larger total binary sizes if multiple object files contain duplicate template instantiations. For large projects with many source files, the compiled library mode offers better build performance.
Implementation Examples
The following examples demonstrate how to activate header-only mode manually and through CMake.
Manual Macro Definition
Define the macro before the first {fmt} include to enable header-only mode for that translation unit:
#define FMT_HEADER_ONLY
#include <fmt/format.h>
int main() {
fmt::print("Hello, {}!\n", "header-only world");
// No -lfmt or fmt.lib required during linking
}
Using the CMake Target
Link against the fmt-header-only target to automatically apply the macro definition across your project:
cmake_minimum_required(VERSION 3.8)
project(example)
add_subdirectory(fmt) # Assuming fmt source is available
add_executable(app main.cpp)
target_link_libraries(app PRIVATE fmt-header-only)
This approach defines FMT_HEADER_ONLY=1 via the target's interface compile definitions, as implemented in CMakeLists.txt at line 420.
Checking the Current Mode
You can verify the mode at compile time if needed:
#include <fmt/format.h>
#ifdef FMT_HEADER_ONLY
// Code specific to header-only builds
#endif
Key Source Files
The header-only mechanism is implemented across these critical files in the fmtlib/fmt repository:
include/fmt/format.h: Contains the primary formatting logic and guarded export blocks (lines ~1351–1360) that control symbol visibility based onFMT_HEADER_ONLY.include/fmt/base.h: Houses low-level utilities and type definitions with similar conditional compilation guards.include/fmt/fmt-c.h: Manages Windows-specific definitions and exports using the same macro checks.CMakeLists.txt: Defines thefmt-header-onlyCMake target and setsFMT_HEADER_ONLY=1as an interface compile definition around line 420.
Summary
- The
FMT_HEADER_ONLYmacro enables header-only mode for the {fmt} library, eliminating the need to link againstlibfmt. - When defined, implementation code in
include/fmt/format.hand related headers becomes active, while symbol exports are disabled. - Use this macro for simple projects, embedded systems, or when you cannot manage compiled library dependencies.
- Prefer the compiled library mode for large projects to minimize compile times and binary bloat.
- The CMake target
fmt-header-onlyautomatically configures this macro for your build.
Frequently Asked Questions
Does FMT_HEADER_ONLY affect runtime performance?
No, the FMT_HEADER_ONLY macro does not change the runtime performance characteristics of {fmt}. The same optimization opportunities exist in both modes because the compiler sees identical implementation code. The primary differences are build-time compile duration and whether the code resides in a separate library or is duplicated across translation units.
Can I mix header-only and compiled modes in the same project?
No, you should not mix these modes within a single program. If one translation unit includes {fmt} headers with FMT_HEADER_ONLY defined and another links against the compiled fmt library, you risk violating the One Definition Rule (ODR) and encountering linker errors due to duplicate symbol definitions. Choose one approach for the entire project.
How do I migrate from header-only to compiled library mode?
Remove the #define FMT_HEADER_ONLY directive from your source files (or switch from fmt-header-only to the fmt CMake target), then ensure you link against the compiled library. In CMake, change target_link_libraries(target PRIVATE fmt-header-only) to target_link_libraries(target PRIVATE fmt). You may need to add find_package(fmt) or add_subdirectory(fmt) depending on your setup.
Is FMT_HEADER_ONLY required when using {fmt} with package managers?
No, when installing {fmt} through package managers like Conan, vcpkg, or system package managers, you typically receive the compiled library by default. The FMT_HEADER_ONLY macro is only necessary if you specifically want header-only behavior or are copying the source headers directly into your project without building the library.
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 →