From 1a9f487a90fb540b71be4c32369de61e216ac7ba Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Wed, 26 Aug 2026 12:52:33 +0300 Subject: [PATCH] Make fmt::as_identifiers a struct instead of a variable The annotation was a constant of an empty type, which left no room for options. Make the annotation type itself, fmt::as_identifiers, the thing users write, so it is now spelled enum class [[=fmt::as_identifiers()]] color { red, green, blue }; and options can later be added as constructor arguments. Co-Authored-By: Claude Opus 5 (1M context) --- CMakeLists.txt | 2 +- doc/api.md | 2 +- include/fmt/enum.h | 9 +++------ test/enum-test.cc | 33 +++++++++++++++++++-------------- test/module-test.cc | 2 +- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 705fe463b273..01b282dbf999 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,7 +414,7 @@ endfunction () set(FMT_REFLECTION_TEST_CODE " #include - enum class [[=fmt::as_identifiers]] color { red }; + enum class [[=fmt::as_identifiers()]] color { red }; static_assert(fmt::is_formattable::value, \"\"); int main() {} ") diff --git a/doc/api.md b/doc/api.md index fb39083ec7c5..131f2f1208f5 100644 --- a/doc/api.md +++ b/doc/api.md @@ -566,7 +566,7 @@ enumerator matching the formatted value: #include - enum class [[=fmt::as_identifiers]] color { red, green, blue }; + enum class [[=fmt::as_identifiers()]] color { red, green, blue }; fmt::print("{}", color::green); // Output: green diff --git a/include/fmt/enum.h b/include/fmt/enum.h index 841f8daef3af..8d07db0b29c6 100644 --- a/include/fmt/enum.h +++ b/include/fmt/enum.h @@ -32,21 +32,18 @@ FMT_BEGIN_NAMESPACE #if FMT_USE_REFLECTION -/// The type of the `fmt::as_identifiers` annotation. -FMT_EXPORT struct as_identifiers_t {}; - /** * An annotation that makes an enum format as identifiers of its enumerators. * * **Example**: * - * enum class [[=fmt::as_identifiers]] color { red, green, blue }; + * enum class [[=fmt::as_identifiers()]] color { red, green, blue }; * auto s = fmt::format("{}", color::green); // s == "green" * * A value that doesn't match any enumerator is represented as its underlying * value in decimal before applying string formatting. */ -FMT_EXPORT inline constexpr auto as_identifiers = as_identifiers_t(); +FMT_EXPORT struct as_identifiers {}; namespace detail { @@ -56,7 +53,7 @@ consteval auto use_identifiers() -> bool { if constexpr (!std::is_enum::value) { return false; } else { - return !std::meta::annotations_of_with_type(^^U, ^^as_identifiers_t) + return !std::meta::annotations_of_with_type(^^U, ^^as_identifiers) .empty(); } } diff --git a/test/enum-test.cc b/test/enum-test.cc index 662399e3e5c3..ab17e062e9d8 100644 --- a/test/enum-test.cc +++ b/test/enum-test.cc @@ -21,37 +21,42 @@ TEST(enum_test, no_reflection) { // clang-format doesn't support annotations yet. // clang-format off -enum class [[=fmt::as_identifiers]] color { red, green, blue }; +enum class [[=fmt::as_identifiers()]] color { red, green, blue }; enum class color_without_annotation { red, green, blue }; -enum [[=fmt::as_identifiers]] unscoped_color { unscoped_red, unscoped_green }; -enum class [[=fmt::as_identifiers]] level : unsigned char { low = 1, high = 2 }; -enum class [[=fmt::as_identifiers]] byte_enum : char { one = 1 }; -enum class [[=fmt::as_identifiers]] signed_byte_enum : signed char { minus_one = -1 }; -enum class [[=fmt::as_identifiers]] bool_enum : bool { off = false }; -enum class [[=fmt::as_identifiers]] alias { one = 1, uno = 1 }; -enum class [[=fmt::as_identifiers]] empty_enum {}; +enum [[=fmt::as_identifiers()]] unscoped_color { unscoped_red, unscoped_green }; +enum class [[=fmt::as_identifiers()]] level : unsigned char { + low = 1, + high = 2 +}; +enum class [[=fmt::as_identifiers()]] byte_enum : char { one = 1 }; +enum class [[=fmt::as_identifiers()]] signed_byte_enum : signed char { + minus_one = -1 +}; +enum class [[=fmt::as_identifiers()]] bool_enum : bool { off = false }; +enum class [[=fmt::as_identifiers()]] alias { one = 1, uno = 1 }; +enum class [[=fmt::as_identifiers()]] empty_enum {}; // Dense values: formatted via a lookup table. -enum class [[=fmt::as_identifiers]] dense { d0, d1, d2, d3, d4 }; +enum class [[=fmt::as_identifiers()]] dense { d0, d1, d2, d3, d4 }; // 3 holes out of 10: the sparsest case that still uses a lookup table. -enum class [[=fmt::as_identifiers]] holey { +enum class [[=fmt::as_identifiers()]] holey { h0, h1, h2, h3, h4, h5, h6 = 9 }; // 4 holes out of 11: just too sparse, formatted via a linear search. -enum class [[=fmt::as_identifiers]] sparse { +enum class [[=fmt::as_identifiers()]] sparse { s0, s1, s2, s3, s4, s5, s6 = 10 }; // Values spanning both signs and the extremes of the underlying type. -enum class [[=fmt::as_identifiers]] signed_enum { +enum class [[=fmt::as_identifiers()]] signed_enum { minus_two = -2, minus_one = -1, one = 1 }; -enum class [[=fmt::as_identifiers]] extremes : int { +enum class [[=fmt::as_identifiers()]] extremes : int { lowest = INT_MIN, highest = INT_MAX }; -enum class [[=fmt::as_identifiers]] big : unsigned long long { +enum class [[=fmt::as_identifiers()]] big : unsigned long long { huge = ULLONG_MAX }; // clang-format on diff --git a/test/module-test.cc b/test/module-test.cc index e266a8797e98..57f9534c39e6 100644 --- a/test/module-test.cc +++ b/test/module-test.cc @@ -377,7 +377,7 @@ TEST(module_test, compile_format_string) { defined(__cpp_lib_define_static) // clang-format doesn't support annotations yet. // clang-format off -enum class [[=fmt::as_identifiers]] color { red, green, blue }; +enum class [[=fmt::as_identifiers()]] color { red, green, blue }; // clang-format on TEST(module_test, format_enum) {