Formatting Support
Description
Boost.SafeNumbers supports formatting with both <format> (C++20 and later) and <fmt/format.h> (all language standards).
The formatters delegate to the underlying type’s formatter, so all standard format specifiers for that type are supported.
Formatting is available for every safe number type: the safe integers (u8, u16, u32, u64, u128, i8, i16, i32, i64, i128), the safe floating-point types (f32, f64), and the bounded types (bounded_uint<Min, Max>, bounded_int<Min, Max>, bounded_float<Min, Max>).
The integer format specifiers documented below apply to the integer types; for f32 and f64 the standard floating-point specifiers are forwarded to the underlying float or double formatter, as described in Floating-Point Format Specifiers.
std::format is supported when using C++20 or later with a compiler that has appropriate support: GCC >= 13, Clang >= 18, MSVC >= 19.40.
#include <boost/safe_numbers/format.hpp> // For std::format support
#include <boost/safe_numbers/fmt_format.hpp> // For fmt::format support
Synopsis
// <boost/safe_numbers/format.hpp>
template <detail::library_type T>
struct std::formatter<T>
: std::formatter<detail::underlying_type_t<T>>
{
auto format(const T& val, std::format_context& ctx) const;
};
// <boost/safe_numbers/fmt_format.hpp>
template <detail::library_type T>
struct fmt::formatter<T>
: fmt::formatter<detail::underlying_type_t<T>>
{
auto format(const T& val, fmt::format_context& ctx) const;
};
Type Modifiers
The following type modifiers are supported (same as built-in integer types):
| Modifier | Format | Example |
|---|---|---|
|
Decimal (default) |
|
|
Lowercase hexadecimal |
|
|
Uppercase hexadecimal |
|
|
Octal |
|
|
Lowercase binary |
|
|
Uppercase binary |
|
Example usage for hexadecimal format: {:x}
Alternate Form
Use # to enable alternate form which adds a prefix:
| Format | Prefix |
|---|---|
|
|
|
|
|
|
|
|
|
|
Padding and Alignment
Values can be padded to a fixed width with optional alignment:
| Specifier | Alignment | Example (val = 42) |
|---|---|---|
|
Right (default) |
` 42` |
|
Left |
`42 ` |
|
Center |
` 42 ` |
|
Right with zero fill |
|
Fill Character
A custom fill character can be specified before the alignment:
-
{:*>10}produces**42 -
{:_<10}produces42__
Sign Modifier
For unsigned types, sign modifiers have limited effect since values are always non-negative:
| Modifier | Effect |
|---|---|
|
Always show sign (shows |
|
Only show sign for negative (no effect for unsigned) |
` ` |
Space for positive, minus for negative |
Locale Modifier
Use L to apply locale-specific formatting (e.g., thousand separators):
std::locale::global(std::locale("en_US.UTF-8"));
std::cout << std::format("{:L}", u32{1234567}); // "1,234,567"
Floating-Point Format Specifiers
For f32 and f64 the formatter forwards to the underlying float or double formatter, so the standard floating-point presentation types and precision apply instead of the integer type modifiers above.
| Presentation | Meaning |
|---|---|
(default) |
Shortest representation that round-trips to the same value |
|
Fixed-point notation |
|
Scientific notation |
|
General: fixed or scientific, whichever is shorter |
|
Hexadecimal floating-point notation |
A precision is written after a dot: {:.2f} formats f64{3.14159} as 3.14.
The fill, alignment, width, sign, and locale specifiers described above apply to the floating-point types as well.
Format Specifier Order
The full format specifier order is:
{[fill][align][sign][#][0][width][.precision][L][type]}
Examples
The header <boost/safe_numbers/fmt_format.hpp> is NOT part of the convenience header, because it is an optional dependency on a potentially compiled library.
|
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#define FMT_HEADER_ONLY
#if __has_include(<fmt/format.h>)
#include <boost/safe_numbers/unsigned_integers.hpp>
#include <boost/safe_numbers/bounded_integers.hpp>
#include <boost/safe_numbers/fmt_format.hpp>
#include <fmt/format.h>
#include <iostream>
int main()
{
using namespace boost::safe_numbers;
const u32 val1 {12345};
const u64 val2 {9876543210};
// Default format (decimal)
std::cout << "Default Format:\n";
std::cout << fmt::format("{}", val1) << '\n';
std::cout << fmt::format("{}", val2) << "\n\n";
// Hexadecimal format
std::cout << "Hexadecimal Format:\n";
std::cout << fmt::format("{:x}", val1) << '\n';
std::cout << fmt::format("{:#x}", val2) << "\n\n";
// Binary format
std::cout << "Binary Format:\n";
std::cout << fmt::format("{:b}", val1) << '\n';
std::cout << fmt::format("{:#b}", u8{42}) << "\n\n";
// Octal format
std::cout << "Octal Format:\n";
std::cout << fmt::format("{:o}", val1) << '\n';
std::cout << fmt::format("{:#o}", val1) << "\n\n";
// Padding and alignment
std::cout << "Padding and Alignment:\n";
std::cout << fmt::format("{:>10}", val1) << '\n'; // Right align
std::cout << fmt::format("{:<10}", val1) << '\n'; // Left align
std::cout << fmt::format("{:^10}", val1) << '\n'; // Center align
std::cout << fmt::format("{:0>10}", val1) << "\n\n"; // Zero-padded
// Fill character
std::cout << "Fill Character:\n";
std::cout << fmt::format("{:*>10}", val1) << '\n';
std::cout << fmt::format("{:_<10}", val1) << "\n\n";
// Bounded integer types
using percent = bounded_uint<0u, 100u>;
using port = bounded_uint<1u, 65535u>;
std::cout << "Bounded Integers:\n";
std::cout << fmt::format("{}", percent{75u}) << '\n';
std::cout << fmt::format("{:>8}", port{8080u}) << '\n';
std::cout << fmt::format("{:#x}", port{443u}) << '\n';
return 0;
}
#else
#include <iostream>
int main()
{
std::cout << "{fmt} headers are required to run this example." << std::endl;
}
#endif
Output:
Default Format:
12345
9876543210
Hexadecimal Format:
3039
0x24cb016ea
Binary Format:
11000000111001
0b101010
Octal Format:
30071
030071
Padding and Alignment:
12345
12345
12345
0000012345
Fill Character:
*****12345
12345_____
Bounded Integers:
75
8080
0x1bb
This same example can be run with <format> by replacing fmt::format with std::format and including <boost/safe_numbers/format.hpp> instead.