How to Use Positional Arguments in fmtlib for Localization
The {fmt} library allows you to combine explicit positional indices like {0} or {1} with the L format specifier to apply locale-specific formatting (digit grouping, decimal separators) to individual arguments while maintaining complete control over argument ordering.
The fmtlib/fmt repository provides a type-safe C++ formatting library that handles complex localization requirements through its positional argument syntax. By referencing specific argument indices in your format string, you can reorder values for different languages and apply localized formatting only where needed. This article examines the internal parsing architecture and practical usage patterns for positional arguments in fmtlib for localization based on the current source tree.
Understanding the Syntax
A replacement field in {fmt} follows the structure {index[:format][L][!conversion][width][precision]}. The argument index selects which positional argument to format, while the L specifier activates locale-aware output for that specific field. These components operate independently, allowing precise control over both argument selection and localization.
The parser processes these fields in two distinct stages. First, it tokenizes the format string and builds a format_specs object for each replacement field. When the parser encounters an explicit index, it stores that positional reference; when it encounters L, it sets the localization flag via format_specs::localized().
Core Architecture
Argument Index Resolution
In include/fmt/format.h, the format string parser resolves positional arguments during the initial tokenization phase. According to the source around line 2218, the library constructs a format_specs object that captures both the explicit argument index (if provided) and the presence of the L conversion specifier. If you omit the index, the library uses the "next argument" rule, automatically incrementing to the following positional parameter.
Localization Flag Processing
The format_specs::localized() method indicates whether the L specifier is present in the current field. This flag is defined in include/fmt/core.h alongside related utilities like localized_mask and set_localized(). The flag remains independent of the argument index, meaning you can apply localization to any positional argument regardless of its position in the argument list.
Locale-Aware Write Path
During the final formatting stage, the library checks the localization flag to determine which output path to take. In include/fmt/format.h around line 2371, the code invokes locale-aware write functions (write_loc) when localized() returns true. These functions utilize helpers from include/fmt/locale.h, such as detail::decimal_point (referenced around line 2793), to retrieve the correct decimal separators and digit grouping characters for the current locale.
Practical Code Examples
The following examples demonstrate combining positional arguments with localization in various contexts:
#include <fmt/core.h>
#include <fmt/locale.h>
#include <locale>
int main() {
// 1️⃣ Simple positional arguments (no localization)
std::string s1 = fmt::format("I’d rather be {1} than {0}.", "right", "happy");
// → "I’d rather be happy than right."
// 2️⃣ Localized integer with grouping (uses the current locale)
std::locale loc("en_US.UTF-8"); // comma as thousands separator
std::string s2 = fmt::format(loc, "Amount: {0:L}", 1234567);
// → "Amount: 1,234,567"
// 3️⃣ Mixing positional index and localization with precision
double v = 12345.6789;
std::string s3 = fmt::format(loc, "Value: {1:.2L} ({0})", "raw", v);
// → "Value: 12,345.68 (raw)"
// 4️⃣ Localized date/time (uses fmt::chrono.h, also supports positional args)
#include <fmt/chrono.h>
auto now = std::chrono::system_clock::now();
std::string s4 = fmt::format(loc, "Current time: {0:%cL}", now);
// → locale‑specific full date‑time string
}
Key Implementation Files
Understanding the following source files provides deeper insight into how positional arguments and localization interact:
-
include/fmt/format.h— Contains the core parser that handlesformat_specsconstruction, argument index resolution, and thewrite_locimplementation for localized output. -
include/fmt/core.h— Defines thelocalized_maskandset_localized()utilities used during format specification parsing. -
include/fmt/locale.h— Provides locale-aware helpers includingdetail::decimal_pointfor retrieving culture-specific separators. -
include/fmt/chrono.h— Implements localized date-time formatting using the same positional argument mechanics. -
include/fmt/printf.h— Demonstrates POSIX-style positional syntax (*1$) for width and precision in printf-compatible APIs.
Summary
- Positional arguments use explicit indices (
{0},{1}) to select specific arguments, enabling reordering for different languages or reuse of the same argument multiple times. - The
Lspecifier activates locale-aware formatting for individual fields, applying digit grouping and proper decimal separators based on the provided locale. - The parsing architecture separates argument index resolution from localization flag detection in
include/fmt/format.h, allowing these features to combine freely. - Locale-aware output occurs through specialized write paths that consult
detail::decimal_pointand related utilities frominclude/fmt/locale.h. - Both features work with chrono types via
include/fmt/chrono.h, supporting localized date and time formatting with positional indices.
Frequently Asked Questions
Can positional arguments be used with automatic indexing in the same format string?
No, {fmt} requires you to choose between automatic indexing (omitting indices) and manual positional indexing. If you provide an explicit index for any argument, you must provide explicit indices for all arguments in that format string. The parser enforces this consistency to prevent ambiguity about which argument corresponds to which replacement field.
Does the L specifier work with all data types?
The L specifier primarily affects arithmetic types (integers, floating-point numbers) and chrono types. For arithmetic types, it enables digit grouping and locale-specific decimal points. For chrono types in include/fmt/chrono.h, it produces locale-specific date and time representations. String and pointer types generally ignore the L specifier because they do not have locale-specific formatting variations.
How does fmtlib handle locales with different grouping conventions?
The library uses the detail::decimal_point helper and related locale facets defined in include/fmt/locale.h to query the current locale's numpunct facet. This provides the thousands separator character (such as comma, period, or space) and the grouping pattern (such as three digits or Indian-style grouping). The write_loc function in include/fmt/format.h applies these settings when the L flag is set.
Are positional arguments supported in printf-style formatting?
Yes, {fmt} supports POSIX-style positional arguments in its printf API via include/fmt/printf.h. You can use the %1$d syntax to reference the first argument, %2$d for the second, and so on. The library also supports positional width and precision using the *1$ notation, where the asterisk references a specific argument for the width or precision value.
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 →