# How to Format Enums with fmtlib: Identifier Names vs Integer Values

> Learn to format enums with fmtlib by printing enumerator names using [[=fmt::as_identifiers]] or integer values. Control your enum output easily.

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

---

**To format enums with fmtlib, annotate your enum with `[[=fmt::as_identifiers]]` to print enumerator names, or leave it unannotated to print the underlying integer value.**

The fmtlib/fmt library supports two distinct modes for enum formatting: default integer output and identifier-based string representation. When you format enums with fmtlib, the library checks for the presence of the `fmt::as_identifiers` annotation at compile time to determine which formatting strategy to apply. This behavior is implemented in [`include/fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/enum.h) and leverages C++ reflection facilities when available.

## Default Behavior vs Identifier-Based Formatting

fmtlib handles enums through two primary code paths depending on whether you opt into identifier formatting.

### Default Behavior: Printing Underlying Integer Values

By default, unannotated enums format as their underlying integral values. The generic `formatter` specialization calls `detail::write` on the enum's underlying type, producing numeric output suitable for debugging or logging scenarios where symbolic names are unnecessary.

This fallback mechanism resides in [`include/fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/enum.h) at lines 99-107, where the formatter handles cases where identifier lookup fails or reflection is unavailable.

### Named Enum Formatting with `fmt::as_identifiers`

To display enumerator names instead of integers, annotate your enum declaration with `[[=fmt::as_identifiers]]`. This attribute, defined in [`include/fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/enum.h) (lines 36-50), activates a compile-time formatter that converts enum values to their string identifiers.

The detection occurs through `detail::use_identifiers<E>()`, a constexpr function (lines 54-62) that checks for the annotation. When present, the formatter retrieves the identifier via `detail::identifier_of`, which constructs a perfect hash table or open-addressed map from reflection metadata (lines 64-82 and 164-176). If the identifier exists, the formatter delegates to `formatter<string_view>`; otherwise, it falls back to the integer representation at lines 98-107.

## Internal Mechanism of Enum Formatting

The identifier-based system relies on compile-time reflection infrastructure controlled by the `FMT_USE_REFLECTION` macro.

### Compile-Time Detection and Lookup

**Annotation detection** happens via `detail::use_identifiers<E>()`, which returns `true` only for annotated enums. This triggers the construction of **constexpr lookup tables** through `make_identifier_table` or `make_identifier_map` (lines 84-112 and 119-127), creating arrays that map enum values to string literals.

**Identifier retrieval** uses `detail::identifier_of(value)` to perform constant-time lookups via direct indexing or open-addressed probing (lines 164-176). The function returns a `string_view` representing the enumerator name, which the `formatter<E, char>` then passes to the standard string formatter.

### Reflection Requirements

When `FMT_USE_REFLECTION` is enabled (requiring compiler support and the `<version>` header), fmtlib exposes the identifier-based formatter. Without reflection support, the library omits this functionality, and all enums format as integers regardless of annotation.

## Practical Code Examples

The following examples demonstrate how to format enums with fmtlib in real-world scenarios.

### Basic Identifier Formatting

Include [`fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/fmt/enum.h) and apply the annotation to print enum names:

```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::blue);   // Output: blue (width applies to string)
}

```

### Integer Fallback for Unannotated Enums

Without the annotation, fmtlib prints the underlying value:

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

enum class Status { ok = 0, warning = 1, error = 2 };

int main() {
    fmt::print("{}\n", Status::warning);   // Output: 1
}

```

### Mixed Usage in Same Program

You can combine annotated and unannotated enums within the same format string:

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

enum class [[=fmt::as_identifiers]] State { idle, running, stopped };
enum class Level { low = 10, medium = 20, high = 30 };

int main() {
    fmt::print("State: {}, Level: {}\n",
               State::running,   // prints "running"
               Level::medium); // prints "20"
}

```

### Custom Fallback Handling

For handling potentially invalid enum values safely, wrap the enum and check for empty identifiers:

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

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

struct SafeError {
    ErrorCode code;
};

template <> struct fmt::formatter<SafeError> {
    fmt::formatter<std::string_view> sv;
    constexpr auto parse(fmt::format_parse_context& ctx) { return sv.parse(ctx); }
    
    template <typename Ctx>
    auto format(const SafeError& e, Ctx& ctx) const {
        auto id = fmt::detail::identifier_of(e.code);
        return id.empty() ? sv.format("UNKNOWN", ctx) : sv.format(id, ctx);
    }
};

int main() {
    fmt::print("{}\n", SafeError{static_cast<ErrorCode>(123)}); // Output: UNKNOWN
}

```

This custom formatter reuses the internal `identifier_of` function from [`include/fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/enum.h) while providing application-specific fallback logic.

## Summary

- **Default formatting** prints the underlying integer value via `detail::write` when enums lack the `fmt::as_identifiers` annotation.
- **Identifier formatting** requires the `[[=fmt::as_identifiers]]` attribute and C++ reflection support (`FMT_USE_REFLECTION`).
- **Lookup mechanism** uses constexpr perfect hash tables built by `make_identifier_table` or `make_identifier_map` in [`include/fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/enum.h).
- **Fallback behavior** automatically prints integers when identifier lookup fails or reflection is unavailable.
- **Custom formatters** can leverage `fmt::detail::identifier_of` to implement specialized error handling while maintaining the library's lookup performance.

## Frequently Asked Questions

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

fmtlib prints the underlying integral value of the enumerator. The generic formatter in [`include/fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/enum.h) calls `detail::write` on the enum's base type, producing numeric output identical to formatting the equivalent integer.

### Does identifier-based enum formatting require C++23 reflection?

Yes, the identifier-based functionality requires compiler support for C++ reflection (`std::meta`), controlled by the `FMT_USE_REFLECTION` macro. When reflection is unavailable, the library omits the `as_identifiers` formatter and falls back to integer output even for annotated enums.

### Can I apply formatting specifiers like width and precision to enum names?

Yes, when using `[[=fmt::as_identifiers]]`, the formatter delegates to `formatter<string_view>`, which respects standard string formatting specifiers. Width, precision, and alignment apply to the enumerator name string rather than the numeric value.

### How does fmtlib handle invalid or out-of-range enum values?

When an enum value has no corresponding identifier in the compile-time lookup table, `detail::identifier_of` returns an empty `string_view`. The formatter then falls back to printing the underlying integer value (lines 98-107 in [`include/fmt/enum.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/enum.h)). For custom behavior, implement a specialized `formatter` that checks for empty identifiers before delegating to the default implementation.