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 types
  • arithmetic_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 %f for int_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 of enum class type, type_constant mapping, and bit-set utilities
  • include/fmt/format.h — Formatter dispatch logic that consumes type values for compile-time checks
  • include/fmt/args.h — Runtime argument storage using type enum in variadic argument lists
  • include/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 typeid and dynamic_cast overhead
  • Platform-conditional members — int128_type and uint128_type appear only when __int128 is 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::type enumeration in include/fmt/core.h categorizes 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 type values enables efficient variadic argument processing without RTTI
  • User-defined types automatically map to custom_type when formatter<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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →