fmtlib Type Enumeration: How {fmt} Maps C++ Types to Compile-Time Formatters
The fmt::type enumeration in include/fmt/core.h classifies every format argument into a compact set of categories, enabling zero-overhead dispatch and type-safe formatting without RTTI.
The {fmt} library achieves its renowned performance and type safety through a carefully designed type system centered on enum class type. This enumeration, defined at lines 73-94 of include/fmt/core.h, serves as the foundation for how the library understands, stores, and processes format arguments at both compile time and run time.
What Is the fmtlib Type Enumeration?
The type enumeration lists every fundamental argument category the library can format:
| Enumerator | Represents | Typical C++ Types |
|---|---|---|
none_type |
Placeholder / no value | — |
int_type |
Signed integers | int |
uint_type |
Unsigned integers | unsigned |
long_long_type |
64-bit signed integers | long long |
ulong_long_type |
64-bit unsigned integers | unsigned long long |
int128_type / uint128_type |
128-bit integers (platform-dependent) | __int128 / unsigned __int128 |
bool_type |
Boolean values | bool |
char_type |
Character code units | char, wchar_t, char8_t, char16_t, char32_t |
float_type |
Single-precision floats | float |
double_type |
Double-precision floats | double |
long_double_type |
Extended-precision floats | long double |
cstring_type |
C-style null-terminated strings | const char*, const wchar_t*, etc. |
string_type |
String views | basic_string_view<Char> |
pointer_type |
Raw pointers | const void* |
custom_type |
User-defined types with formatters | Any type with fmt::formatter<T> specialization |
This compact representation—typically fitting in a single byte—allows {fmt} to handle type information with minimal memory overhead and zero runtime type identification (RTTI) costs.
Three Core Purposes of the Type Enumeration
1. Compile-Time Type Dispatch
The type_constant<T, Char> template maps concrete C++ types to their corresponding type enumerators. This mapping happens entirely at compile time.
When you write fmt::print("{}", 42), the library resolves type_constant<int, char> to type::int_type. This compile-time resolution eliminates virtual function calls and enables aggressive inlining for hot formatting paths.
2. Fast Type Category Testing
Helper bit-set constants defined around lines 1035-1045 in include/fmt/core.h use the enum values to build compile-time masks:
sint_set— signed integer types (int_type,long_long_type,int128_type)uint_set— unsigned integer types (uint_type,ulong_long_type,uint128_type)float_set— floating-point types (float_type,double_type,long_double_type)integral_set— all integer typesarithmetic_set— all numeric types
These bit sets power functions like is_integral_type(type t) and is_arithmetic_type(type t), enabling single-instruction type category checks.
3. Runtime Argument List Representation
When formatting variadic templates, each argument's type is stored as a type value in the internal argument list. This compact metadata allows the library to:
- Iterate over heterogeneous argument sequences
- Validate format specifiers against actual types (e.g., rejecting
%fforint_type) - Select the correct formatting implementation without type erasure penalties
Practical Code Examples
Basic Type Mapping
#include <fmt/core.h>
#include <fmt/format.h>
int main() {
// Automatically maps to type::int_type
fmt::print("Integer: {}\n", 42);
// Automatically maps to type::double_type
fmt::print("Float: {}\n", 3.14159);
// Automatically maps to type::string_type
fmt::print("String: {}\n", std::string_view("hello"));
}
Custom Type Classification
#include <fmt/core.h>
#include <fmt/format.h>
struct Point {
int x, y;
};
// Custom formatter specialization
template<>
struct fmt::formatter<Point> {
constexpr auto parse(fmt::format_parse_context& ctx) {
return ctx.begin();
}
template<typename FormatContext>
auto format(const Point& p, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
int main() {
// Point is classified as type::custom_type
fmt::print("Point: {}\n", Point{1, 2});
}
In this example, Point lacks a built-in type_constant mapping, so the library routes it through type::custom_type and invokes the user-provided formatter<Point> specialization.
Key Implementation Files
include/fmt/core.h— Definition ofenum class type,type_constantmapping, and bit-set utilitiesinclude/fmt/format.h— Formatter dispatch logic that consumestypevalues for compile-time checksinclude/fmt/args.h— Runtime argument storage usingtypeenum in variadic argument listsinclude/fmt/enum.h— Extended enum formatting support (separate from the core type system)
Design Trade-offs and Performance Characteristics
The type enumeration reflects deliberate engineering decisions:
- Fixed enumeration vs. open type system — User-defined types must provide
formatter<T>specializations; there is no dynamic registration - No RTTI dependency — All type information is either compile-time constant or explicitly stored, avoiding
typeidanddynamic_castoverhead - Platform-conditional members —
int128_typeanduint128_typeappear only when__int128is supported by the compiler
These constraints enable {fmt} to format arguments with throughput approaching manual std::to_string calls while maintaining full type safety.
Summary
- The
fmt::typeenumeration ininclude/fmt/core.hcategorizes all formatable values into 15+ fundamental types - Compile-time dispatch via
type_constant<T, Char>eliminates virtual calls and enables inlining - Bit-set constants built from enum values provide single-instruction type category testing
- Runtime storage of
typevalues enables efficient variadic argument processing without RTTI - User-defined types automatically map to
custom_typewhenformatter<T>specializations are provided
Frequently Asked Questions
How does {fmt} handle types not in the enum?
Types without built-in mappings are classified as type::custom_type. The library requires a fmt::formatter<T> specialization for such types, which it invokes through the custom type dispatch path. No runtime registration or dynamic type discovery is supported.
Is there overhead from storing type information at runtime?
No meaningful overhead. Each argument stores one byte (or less) of type metadata. This is comparable to manual variadic template expansion and far cheaper than std::any or virtual function approaches used by some alternative formatting libraries.
Why use an enum instead of template specializations alone?
The enum enables uniform runtime representation for type checking and dispatch. While template specializations handle the actual formatting, the type value allows format string validation and argument iteration to work efficiently across heterogeneous parameter packs without instantiating per-type code paths for control flow.
Does the enumeration vary across platforms?
Yes, minimally. The int128_type and uint128_type enumerators are conditionally defined based on compiler support for 128-bit integers. All other enumerators are stable across platforms and character types (char, wchar_t, etc.).
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 →