What Is FMT_HEADER_ONLY? Benefits of Header-Only Mode in fmtlib
Defining FMT_HEADER_ONLY switches {fmt} to a header-only configuration that eliminates the need to link against a separate library binary by instantiating all formatting templates inline within each translation unit.
The {fmt} library provides fast, type-safe text formatting for C++. By default, it compiles into a standalone library (libfmt) that you link against your application. However, the FMT_HEADER_ONLY preprocessor macro offers an alternative compilation model that embeds the entire library directly into your source code, providing distinct advantages for specific deployment scenarios.
How FMT_HEADER_ONLY Works Internally
When you define FMT_HEADER_ONLY, you change how the compiler handles template instantiation. In the standard compiled mode, {fmt} uses explicit template instantiations declared with extern template to minimize compilation overhead. These declarations appear in include/fmt/format.h at lines 1351–1360, where they instruct the compiler to expect定型版 (pre-compiled) template instances in the linked library.
// From include/fmt/format.h (lines 1351-1360)
#ifndef FMT_HEADER_ONLY
# define FMT_EXTERN template
extern template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
// ... additional extern template declarations
#else
# define FMT_EXTERN
#endif
When FMT_HEADER_ONLY is defined, the preprocessor skips these extern template guards. Consequently, the full template definitions remain visible in the header, forcing the compiler to generate inline instantiations in every translation unit that includes the library. This mechanism effectively converts {fmt} from a compiled binary dependency into a header-only library similar to single-file utilities.
Five Key Benefits of Using FMT_HEADER_ONLY
No Separate Library Binary Required
The most immediate benefit is the elimination of the linking step. You no longer need to build or distribute libfmt, whether as a static (.a/.lib) or shared (.so/.dll) object. This is particularly valuable for embedded systems environments or educational projects where managing compiled dependencies adds unnecessary complexity. As implemented in include/fmt/format.h, the macro ensures all implementation details remain available to the compiler without requiring external symbols.
Faster Build Times for Small Projects
Single-file utilities and small applications often compile faster in header-only mode because they bypass the overhead of building a separate library target. According to the CMake configuration in CMakeLists.txt at line 456, the fmt-header-only interface target simply adds the macro definition without producing a compiled artifact:
# CMakeLists.txt defines the header-only target
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
This eliminates the need to compile the library's translation units separately and streamlines the build graph in monolithic projects.
Reduced Binary Size via Dead Code Elimination
Header-only mode enables aggressive dead code elimination. Because templates are instantiated on-demand rather than pulled from a pre-compiled library, the linker sees only the specific formatting functions your code actually uses. Unused specializations—such as exotic floating-point formatters or wide-character handlers—can be discarded during the linking phase. The {fmt} README highlights this characteristic in its "Small code size" section, noting that header-only usage allows the compiler to strip away unused formatting machinery that would otherwise remain in a monolithic library binary.
Simplified Cross-Platform Dependency Management
Distributing compiled binaries across heterogeneous environments (Windows, Linux, macOS, various embedded targets) requires managing platform-specific artifacts and ABI compatibility. FMT_HEADER_ONLY removes this burden entirely. Since the implementation resides solely in headers (include/fmt/format.h, include/fmt/base.h), a single source tree works everywhere the compiler supports C++11 or later. This portability is documented in doc/api.md (lines 83–85), which lists the macro as a configuration option for projects requiring maximum portability without additional artifacts.
Seamless Integration with Header-Only Ecosystems
Modern C++ projects often rely on header-only libraries like nlohmann/json or spdlog to minimize dependency friction. FMT_HEADER_ONLY allows {fmt} to fit naturally into these ecosystems. The macro is detected early in the preprocessor phase (around line 4607 in include/fmt/format.h), enabling the same source tree to function as either a compiled library or a drop-in header-only dependency based on a single configuration flag.
Implementation Examples
Manual Macro Definition
To use {fmt} as a pure header-only library, define the macro before including any headers:
#define FMT_HEADER_ONLY
#include <fmt/format.h>
#include <fmt/ostream.h>
int main() {
// No need to link against libfmt
fmt::print("Hello, {}!\n", "world");
std::string s = fmt::format("Answer: {}", 42);
fmt::print("{}\n", s);
}
This approach requires no changes to your build system beyond ensuring the include directory is in your compiler's search path.
CMake Configuration
For CMake-based projects, use the provided interface target rather than manually defining the macro:
cmake_minimum_required(VERSION 3.14)
project(myapp)
find_package(fmt REQUIRED)
add_executable(myapp main.cpp)
# Use the header-only interface target
target_link_libraries(myapp PRIVATE fmt::fmt-header-only)
The fmt::fmt-header-only target automatically propagates the FMT_HEADER_ONLY definition and include directories to your executable.
Conditional Compilation
For libraries that must support both compiled and header-only modes, conditionally include the macro:
#if defined(FMT_HEADER_ONLY)
#include <fmt/format.h>
#else
#include <fmt/format.h>
#pragma comment(lib, "fmt") // Windows MSVC example
#endif
This pattern allows downstream consumers to choose their preferred linking strategy without modifying your library's source code.
Summary
FMT_HEADER_ONLYconverts {fmt} from a compiled library into a header-only dependency by disablingextern templatedeclarations ininclude/fmt/format.h.- Zero linking required: Eliminates the need to build or distribute
libfmtbinaries, simplifying deployment across platforms. - Optimized binaries: Template instantiation on-demand enables the linker to discard unused formatting code, reducing executable size.
- Faster iteration: Small projects benefit from streamlined build processes without separate library compilation steps.
- CMake support: The
fmt::fmt-header-onlytarget provides idiomatic integration for modern build systems.
Frequently Asked Questions
What is the difference between FMT_HEADER_ONLY and the compiled library?
The compiled library pre-instantiates common template specializations and exports them as symbols in libfmt, requiring you to link against the binary. FMT_HEADER_ONLY keeps all template definitions in the headers (include/fmt/format.h and include/fmt/base.h), causing the compiler to generate code inline for each translation unit. This trades potentially larger object files for the convenience of header-only distribution.
Does FMT_HEADER_ONLY increase compilation times?
For small projects with few translation units, header-only mode often reduces total build time by eliminating the library compilation step. However, for large codebases with hundreds of translation units all including {fmt} headers, the repeated template instantiation may increase overall compilation time compared to linking against a pre-compiled library. The impact depends on your project's structure and compiler optimization settings.
When should I avoid using FMT_HEADER_ONLY?
Avoid header-only mode when binary size is critical and your application uses {fmt} across many translation units, as each unit will contain its own copy of the template instantiations. Additionally, if you need to minimize compile times in large projects or if you're distributing {fmt} as a system package where ABI stability matters, the compiled library provides better isolation and faster incremental builds.
How do I migrate from the compiled library to header-only mode?
Migration requires two steps: first, add #define FMT_HEADER_ONLY before your include statements or switch to the fmt::fmt-header-only CMake target; second, remove libfmt from your linker flags and dependency lists. Verify that your build system no longer references the compiled library artifacts, and ensure all translation units that include {fmt} headers have access to the macro definition to avoid One Definition Rule (ODR) violations.
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 →