Text formatting is a fundamental aspect of any software project, and in C++, it often boils down to a choice between compromises. On one hand, C-style functions like printf offer speed but lack in safety and type-safety. On the other, iostream classes like stringstream guarantee type-safety but introduce performance overhead and verbosity. This dichotomy has led to a persistent problem in many enterprise codebases: slow, hard-to-maintain, and potentially format-vulnerable code.
When I worked on a high-frequency data processing system handling millions of records per second, the need for efficient, clear logging was critical. Every millisecond counted, and formatting errors could lead to unreadable logs or, worse, production crashes. This is where fmtlib/fmt comes in, offering a solution that combines the best of both worlds: the speed of printf with the safety and flexibility of a modern C++ library.
Tested on: Ubuntu 22.04 LTS · GCC 11.4 · fmtlib 10.1.1 · September 2026
Prerequisites / Test Environment
To follow this guide, you will need a C++ development environment with a modern compiler (GCC 9+ or Clang 9+). fmt can be easily integrated via build systems like CMake or Vcpkg. For our purposes, we will use CMake.
Ensure you have CMake installed:
sudo apt update
sudo apt install build-essential cmake
1. Introduction to fmtlib/fmt
fmt is an open-source library for text formatting in C++. It was designed to be a modern, fast, and safe solution, and its influence is such that it was adopted as the basis for the C++20 standard library . It offers a syntax similar to Python’s f-strings or Rust, allowing you to create formatted strings intuitively and readably.
Why choose fmt over printf and iostream?
- Type-Safe Security: Unlike
printf,fmtchecks argument types at compile time, preventing common errors like format specifier and data type mismatches. - Performance:
fmtis significantly faster thaniostreamand, in many scenarios, evenprintf, thanks to internal optimizations and the avoidance of unnecessary overhead. This is crucial in high-throughput applications, such as logging systems or data processing. - Intuitive Syntax: The
{}syntax for placeholders is much clearer and less error-prone thanprintf‘s format specifiers (%d,%s, etc.). - Extensibility: It allows defining custom formatters for user-defined types, making it extremely flexible for complex data structures. Read also: How to serialize C++ objects to JSON for logging
2. Installation and Basic Usage of fmt
The easiest way to integrate fmt into a C++ project is via CMake. Let’s create a simple project.
Create a project directory:
mkdir fmt_example
cd fmt_example
Create a CMakeLists.txt file:
cmake_minimum_required(VERSION 3.15)
project(FmtExample CXX)
find_package(fmt CONFIG REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE fmt::fmt)
Create the main.cpp file:
#include <fmt/core.h>
#include <vector>
struct Point {
int x, y;
};
template <typename O>
struct fmt::formatter<Point, O> : fmt::formatter<std::string_view, O> {
template <typename Ctx>
auto format(const Point& p, Ctx& ctx) const {
return fmt::format_to(ctx.out(), "(x:{}, y:{})", p.x, p.y);
}
};
int main() {
// Basic example
fmt::print("Hello, {}! The answer is {}.\n", "world", 42);
// Formatting with positional arguments
fmt::print("I love {0} and {1}. {0} is great!\n", "pizza", "pasta");
// Formatting numbers with precision
fmt::print("Value: {:.2f}\n", 3.14159);
// Formatting a vector (requires iteration)
std::vector<int> numbers = {1, 2, 3, 4, 5};
fmt::print("Numbers: {}", fmt::join(numbers, ", "));
fmt::print("\n");
// Formatting a custom type (Point)
Point p = {10, 20};
fmt::print("My point: {}\n", p);
// Writing to a string (similar to stringstream)
std::string s = fmt::format("Formatted string: {}\n", "test");
fmt::print("{}", s);
return 0;
}
Compile and run:
cmake -B build
cmake --build build
./build/my_app
The output will be:
Hello, world! The answer is 42.
I love pizza and pasta. pizza is great!
Value: 3.14
Numbers: 1, 2, 3, 4, 5
My point: (x:10, y:20)
Formatted string: test
Read also: Proxmox VE: Complete Installation & Configuration Guide 2026
3. Advanced Features and Best Practices
fmt offers multiple features beyond basic formatting. Here are some I’ve found particularly useful in production environments:
- Buffer Formatting: For extremely high-performance scenarios,
fmt::format_toandfmt::format_to_nallow writing directly to a pre-allocated buffer, avoiding dynamic allocations. - Logging:
fmtis an excellent choice for logging systems. You can easily integratefmt::printorfmt::formatwith your existing logging framework for faster, safer output. Read also: Implementing an Effective Logging System with rsyslog and ELK Stack - Localization: While not a complete internationalization library,
fmtsupports locale-based formatting for numbers and dates, a crucial aspect in global applications. - Compile-Time Formatting Errors: One of the most powerful features is
fmt‘s ability to detect many formatting errors at compile time, thanks to the use of templates andconstexpr, reducing debugging time.
An example of integration into a logging system could be:
#include <fmt/chrono.h>
#include <fmt/color.h>
#include <fmt/core.h>
#include <chrono>
void log_event(const std::string& level, const std::string& message) {
auto now = std::chrono::system_clock::now();
fmt::print(fmt::fg(fmt::color::gray), "[{:%Y-%m-%d %H:%M:%S}] ", now);
if (level == "ERROR") {
fmt::print(fmt::fg(fmt::color::red), "[{}] {}\n", level, message);
} else if (level == "WARNING") {
fmt::print(fmt::fg(fmt::color::yellow), "[{}] {}\n", level, message);
} else {
fmt::print(fmt::fg(fmt::color::green), "[{}] {}\n", level, message);
}
}
int main() {
log_event("INFO", "Application started.");
log_event("WARNING", "Disk space low: 15% remaining.");
log_event("ERROR", "Failed to connect to database.");
return 0;
}
This example shows how fmt can be used to create colored and time-stamped log outputs efficiently. Read also: Linux Performance Monitoring: 8 Essential Tools Compared
Common Errors and Troubleshooting
fmt::formatvsfmt::print: Remember thatfmt::formatreturns astd::string, whilefmt::printwrites directly tostdout. Choose one or the other based on where the output needs to go. Usingfmt::formatand then printing withcoutorprintfcan negatefmt‘s performance benefits.- Missing
find_package(fmt CONFIG REQUIRED)in CMake: If you receive compilation errors related tofmtor if the linker cannot find symbols, ensure yourCMakeLists.txtcorrectly includes searching for and linking to thefmtlibrary. - Incorrect format specifiers: Although
fmtis type-safe, it is still possible to make errors in the format string, especially with advanced options. Always consult the officialfmtlibdocumentation for correct syntax. A common error is forgetting to includefmt/ostream.hif you want to usefmtwith types that only haveoperator<<defined.
FAQ — Frequently Asked Questions
Is fmtlib compatible with older C++ compilers?
Yes, fmtlib supports C++11 compilers and later. The library is designed to be backward compatible, but the latest features and full optimizations benefit from more modern C++ standards (C++17, C++20).
Can I use fmtlib to format output to files?
Absolutely. fmt::print has overloads that accept a FILE* as the first argument, allowing you to write directly to files. For example, fmt::print(stderr, "Error: {}\n", msg); will write to stderr.
What is the main difference between fmtlib and the C++20 library?
fmtlib is the standalone library that inspired and served as the basis for the C++20 standard library . Many of the features and syntax are identical. The main difference is that is part of the standard and does not require external inclusion, but fmtlib versions may include newer features or extensions not yet standardized.
Does fmtlib support date and time formatting?
Yes, fmt supports date and time formatting through the header, which provides format specifiers similar to strftime but with the safety and speed of fmt.
Conclusions with Operational Takeaways
fmtlib/fmt represents a leap forward in C++ text formatting. Abandoning old printf and iostream habits in favor of fmt is not just a matter of modernity, but a practical necessity for professional C++ software development. The benefits in terms of performance, security, and code readability are tangible and immediate. Adopting fmt means preparing for the future of the C++ standard and improving your code quality today. If you manage complex C++ codebases, the time investment to integrate fmt will quickly pay off in fewer bugs and greater efficiency.
Sources
Updated: September 2026