fmt
fmt quickstart: positional fields, precision and a small custom type
Build a minimal C++20 example, distinguish literal and runtime errors, and extend a domain type with format_as without writing a new parser.
What you will learn
- Get one deterministic example working
- Understand where an error should appear
- Extend the value before extending the syntax
Before you start
- Basic C++ values, references, strings and build targets
- Ability to distinguish a documented expectation from a measured result
Trace concrete fmt behavior and choose an integration with explicit output, lifetime and verification boundaries.
Key takeaways
- Positional fields support reordered messages without changing argument meaning.
- Literal and runtime formats need separate error tests.
- format_as reuses an existing value representation; a custom formatter owns a larger contract.
Get one deterministic example working
Use a small C++20 program that includes fmt/format.h and links the pinned dependency. The example below formats the second argument as a label and the first as a floating-point value with two decimal places. Its expected result is value: 1.23. That single assertion checks the API you need without introducing filesystem writes, remote inputs or an unrelated logging framework.
Replacement fields use braces; a colon introduces a presentation specification. Explicit numeric fields such as {1} and {0:.2f} are useful when the same values appear in different orders in translated messages. Do not mix automatic numeric indexing with explicit numeric indexing in one string. Literal braces are doubled, and formatting rules are not equivalent to printf conversion tokens.
Understand where an error should appear
With supported C++20 consteval checking, passing a string to a decimal-integer presentation in a literal format is a compilation error. Wrapping the same format text with fmt::runtime moves validation to runtime, where the normal exception-enabled configuration can report fmt::format_error. Treat these as deliberately different test cases, not as evidence that runtime input was prevalidated.
The format grammar also permits dynamic width or precision arguments. A valid integer type does not establish an application-specific size limit. For an introductory exercise, keep widths small and fixed, then add a negative test for a type mismatch. If a test unexpectedly compiles, inspect the compiler version, language mode and FMT_USE_CONSTEVAL configuration before changing the assertion.
Extend the value before extending the syntax
For a domain value that should format exactly like an existing type, define format_as in the same namespace as that value. The companion fixture maps a small item containing integer 7 to its integer representation and checks zero-padded output 0007. It reuses the library’s existing integer formatting behavior rather than inventing a parser.
Use a formatter specialization when the representation needs its own parsing or formatting logic. The API documentation recommends reusing an existing formatter through inheritance or composition where possible. Adding both competing extension mechanisms is not a useful shortcut: keep one clear contract and test alignment, width and invalid input behavior for the representation you expose.
Implementation steps
- 1
Build a C++20 target linked to the pinned fmt source.
- 2
Assert the exact positional-and-precision output below.
- 3
Keep a deliberately invalid literal in a separate expected-failure compilation test.
- 4
Add one small domain-type conversion and check its output.
Copy-ready example
#include <fmt/format.h>
#include <cassert>
int main() {
const auto result = fmt::format("{1}: {0:.2f}", 1.234, "value");
assert(result == "value: 1.23");
}Frequently asked questions
Can automatic and explicit numeric indexes be mixed?
No. Use a consistent indexing mode within the format string; the pinned syntax documentation describes the diagnostic.
Is fmt::runtime a way to bypass validation?
It changes when validation occurs. It does not promise that an invalid presentation will succeed or that arbitrary widths are appropriate for your application.
Sources
- doc/get-started.mdSource checked 2026-09-08
- doc/api.mdSource checked 2026-09-08
- doc/syntax.mdSource checked 2026-09-08
- include/fmt/core.hSource checked 2026-09-08
- include/fmt/format.hSource checked 2026-09-08
- test/format-test.ccSource checked 2026-09-08