# How to Use fmtlib's enum.h for Compile-Time-Checked Enum Formatting

> Learn to use fmtlib's enums.h for compile-time-checked enum formatting. This guide shows how to print enumerator names instead of integers for safer, more readable code.

- Repository: [Hello World Foundation/fmt](https://github.com/fmtlib/fmt)
- Tags: how-to-guide
- Published: 2026-09-04

---

**fmtlib provides compile-time-checked enum formatting via the `[[=fmt::as_identifiers]]` annotation in [`include/fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/enum.h), which generates lookup tables at compile time to print enumerator names instead of integer values when C++23 static reflection is available or `FMT_USE_REFLECTION` is enabled.**

The {fmt} library offers a type-safe mechanism for formatting C++ enumerations as human-readable identifier names rather than raw integers. This feature, implemented in [`include/fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/enum.h), leverages static reflection capabilities to validate enum-to-string mappings at compilation time, ensuring that formatting logic never drifts from the actual enum definition.

## Prerequisites: C++23 Reflection and Configuration

Compile-time enum formatting requires compiler support for static reflection, indicated by the `__cpp_impl_reflection` or `__cpp_lib_reflection` feature test macros. If your compiler lacks C++23 reflection support, the entire header effectively disables itself by defining `FMT_USE_REFLECTION` as `0`. You can manually control this behavior by defining `FMT_USE_REFLECTION` when building the library to force the feature on or off regardless of compiler capabilities.

## Annotating Enums with fmt::as_identifiers

To enable identifier formatting, annotate your enum definition with `[[=fmt::as_identifiers]]`. This attribute marks the type for compile-time processing by the formatting machinery.

```cpp
#include <fmt/core.h>
#include <fmt/enum.h>

enum class [[=fmt::as_identifiers]] Color { red, green, blue };

```

The annotation definition resides in [`include/fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/enum.h) at lines 38-50. Only annotated enums receive the specialized formatter; unannotated enums fall back to standard integral formatting, making this an explicit opt-in feature that maintains type safety.

## How the Compile-Time Checking Works

The implementation uses template metaprogramming and `consteval` functions to build lookup structures at compile time. This approach eliminates runtime overhead while guaranteeing that every formatted value corresponds to a valid enumerator or triggers the fallback mechanism.

### Type Detection with use_identifiers()

At lines 54-63, the `detail::use_identifiers<T>()` trait checks whether a type is an enum and carries the `as_identifiers` annotation. This boolean trait controls whether the specialized formatter is instantiated for a given type.

### Lookup Table Generation for Dense Enums

For enums with consecutive values (dense), the library generates a flat lookup table via `make_identifier_table` (lines 100-112). This `identifier_table` stores string views indexed by the enum's underlying value, providing O(1) lookup with minimal memory overhead.

### Hash Map Construction for Sparse Enums

When enum values are sparse or non-consecutive, `make_identifier_map` (lines 140-156) constructs a compile-time hash map (`identifier_map`). This structure handles disconnected value ranges efficiently without wasting storage on empty slots.

### The identifier_of Lookup Function

The `detail::identifier_of(E value)` function (lines 164-182) performs the actual resolution. It returns a `std::string_view` containing the enumerator's identifier if the value exists in the lookup structure, or an empty view if the value does not correspond to a defined enumerator.

### Formatter Specialization and Fallback Behavior

At lines 86-100, a partial `formatter` specialization is enabled for any enum satisfying `detail::use_identifiers<E>()`. The `format()` method first attempts to resolve the identifier via `identifier_of`. If the result is empty (indicating an unknown value), it falls back to formatting the underlying integral value using `underlying(value)`, ensuring that even invalid enum values produce meaningful output.

## Practical Implementation Examples

### Basic Identifier Formatting

The following example demonstrates formatting an annotated enum directly:

```cpp
#include <fmt/core.h>
#include <fmt/enum.h>

enum class [[=fmt::as_identifiers]] Color { red, green, blue };

int main() {
    fmt::print("{}\n", Color::green);          // Output: green
    fmt::print("{:04}\n", Color::red);         // Output: red (width applied)
}

```

### Fallback for Unknown Values

When an enum variable contains a value not present in the definition, the formatter falls back to the numeric representation:

```cpp
enum class [[=fmt::as_identifiers]] ErrorCode { ok = 0, not_found = 404 };

int main() {
    ErrorCode e = static_cast<ErrorCode>(500);   // Undefined enumerator
    fmt::print("{}\n", e);                       // Output: 500
}

```

This fallback occurs because `identifier_of` returns an empty view for unmatched values, triggering the integral formatting path at lines 96-100.

### Explicit Compile-Time Safety

The formatter is only enabled for annotated enums. Attempting to format a non-annotated enum without a custom formatter will use the generic integral formatter, but if you expect identifier formatting, the absence of the annotation serves as a compile-time signal that the type lacks the required metadata:

```cpp
enum class BadEnum { a, b };  // Missing annotation

// fmt::format("{}", BadEnum::a);  // Uses integral formatter, not identifier

```

### Direct Lookup Without Formatting

You can access the compile-time lookup machinery directly for static assertions or constexpr contexts:

```cpp
enum class [[=fmt::as_identifiers]] State { idle, running, stopped };

static_assert(std::string_view(fmt::detail::identifier_of(State::running)) == "running");

```

This utilizes the `constexpr` `identifier_of` function independently of the formatting pipeline.

## Summary

- Compile-time enum formatting in fmtlib requires the `[[=fmt::as_identifiers]]` annotation and C++23 static reflection support (or `FMT_USE_REFLECTION`).
- The implementation in [`include/fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/enum.h) uses `detail::use_identifiers<T>()` to detect annotated enums at compile time.
- Dense enums utilize `make_identifier_table` (lines 100-112) while sparse enums use `make_identifier_map` (lines 140-156) for efficient lookup.
- The `formatter` specialization (lines 86-100) calls `identifier_of` to resolve names, falling back to underlying integral values for unknown enumerators.
- All lookup structures are built using `consteval`/`constexpr` machinery, ensuring errors are caught during compilation rather than at runtime.

## Frequently Asked Questions

### What happens if I format an enum without the fmt::as_identifiers annotation?

Unannotated enums fall back to fmtlib's generic integral formatter, which outputs the underlying numeric value. The specialized identifier formatter is only enabled for types satisfying `detail::use_identifiers<E>()`, ensuring that identifier formatting is always explicit and opt-in.

### Does this feature work with C++17 or C++20 compilers?

No, compile-time-checked enum formatting requires C++23 static reflection capabilities (`__cpp_impl_reflection`). If your compiler does not support this feature, `FMT_USE_REFLECTION` defaults to `0` and the header is effectively disabled unless you manually enable the macro and provide the necessary reflection support yourself.

### How does fmtlib handle enum values that are not defined in the enumeration?

When a value lacks a defined enumerator, `detail::identifier_of` returns an empty `string_view`, causing the formatter to fall back to printing the underlying integral value (as seen at lines 96-100 in [`enum.h`](https://github.com/fmtlib/fmt/blob/main/enum.h)). This ensures robust output even when handling uninitialized or cast enum variables.

### Can I use the identifier lookup without the fmt::format function?

Yes, the `fmt::detail::identifier_of` function is a `constexpr` utility that can be used in any compile-time context, including `static_assert` statements or template metaprogramming. This allows you to leverage the compile-time lookup tables independently of the formatting pipeline.