diff --git a/doc/api.md b/doc/api.md index fb39083e..a8e8da09 100644 --- a/doc/api.md +++ b/doc/api.md @@ -591,20 +591,19 @@ Enums without the annotation are not affected and are formatted as before, i.e. scoped enums require `format_as` or a `formatter` specialization, see [Formatting User-Defined Types](#udt). -Identifiers are retrieved with C++26 reflection ([P2996]( -https://wg21.link/p2996)) and the annotation with [P3394]( -https://wg21.link/p3394), so this requires a compiler with reflection support, +This uses two C++26 features: +[reflection](https://en.cppreference.com/w/cpp/language/operator_reflection) to +retrieve the enumerator identifiers and +[annotations](https://en.cppreference.com/w/cpp/language/annotations) to opt an +enum in via `fmt::as_identifiers`. It therefore requires a compiler with +reflection support, which may need an extra flag such as `-freflection` in GCC. The macro `FMT_USE_REFLECTION` is set to 1 if reflection is available and to 0 otherwise. -It can also be defined by the user to disable the use of reflection, in which -case `fmt/enum.h` is empty. +It can also be defined by the user to disable the use of reflection. When {fmt} is built as a module, reflection support is detected when the module itself is compiled, so this API is only available to importers if the module was -built with reflection enabled. An importing translation unit may also have to -include `` itself: some compilers, such as GCC 16, fail to look up -implementation details of `std::define_static_string` when instantiating the -formatter otherwise. +built with reflection enabled. ## Compile-Time Support diff --git a/include/fmt/enum.h b/include/fmt/enum.h index bbe1a18f..ad863a7f 100644 --- a/include/fmt/enum.h +++ b/include/fmt/enum.h @@ -25,6 +25,7 @@ #if FMT_USE_REFLECTION && !defined(FMT_MODULE) # include # include +# include # include // std::pair #endif @@ -61,13 +62,6 @@ consteval auto use_identifiers() -> bool { } } -// Returns the identifier of e. identifier_of returns a view of a string with -// static storage duration so it doesn't need to be copied. -inline consteval auto identifier(std::meta::info e) -> string_view { - auto id = std::meta::identifier_of(e); - return string_view(id.data(), id.size()); -} - // Returns the underlying value of `value` converted to uint64_t. Negative // values wrap around, so the difference of two such values is the distance // between them. @@ -75,10 +69,6 @@ template constexpr auto to_uint64(E value) -> uint64_t { return static_cast(static_cast>(value)); } -template consteval auto count_enumerators() -> size_t { - return std::meta::enumerators_of(^^E).size(); -} - // Returns the smallest enumerator value of E or 0 if E has no enumerators. template consteval auto min_enumerator() -> E { auto enumerators = std::meta::enumerators_of(^^E); @@ -108,13 +98,15 @@ template consteval auto identifier_table_size() -> size_t { } template ()> -consteval auto make_identifier_table() -> std::array { - auto ids = std::array(); +consteval auto make_identifier_table() -> std::array { + auto ids = std::array(); auto min = to_uint64(min_enumerator()); for (std::meta::info e : std::meta::enumerators_of(^^E)) { auto i = static_cast(to_uint64(std::meta::extract(e)) - min); - // Keep the identifier of the first enumerator with this value. - if (ids[i].size() == 0) ids[i] = identifier(e); + // identifier_of returns a view of a string with static storage duration so + // it doesn't need to be copied. Keep the identifier of the first + // enumerator with this value. + if (ids[i].size() == 0) ids[i] = std::meta::identifier_of(e); } return ids; } @@ -129,7 +121,7 @@ inline constexpr auto identifier_table = make_identifier_table(); // below 0.5, which guarantees a free slot and therefore terminates the search. template consteval auto identifier_map_size() -> size_t { auto size = size_t(1); - while (size < count_enumerators() * 2) size *= 2; + while (size < std::meta::enumerators_of(^^E).size() * 2) size *= 2; return size; } @@ -147,8 +139,8 @@ constexpr auto identifier_slot(E value) -> size_t { template ()> consteval auto make_identifier_map() - -> std::array, N> { - auto map = std::array, N>(); + -> std::array, N> { + auto map = std::array, N>(); for (std::meta::info e : std::meta::enumerators_of(^^E)) { auto value = std::meta::extract(e); for (auto i = identifier_slot(value);; i = (i + 1) & (N - 1)) { @@ -157,7 +149,7 @@ consteval auto make_identifier_map() if (map[i].first == value) break; continue; // The slot is taken by another value; probe the next one. } - map[i] = {value, identifier(e)}; + map[i] = {value, std::meta::identifier_of(e)}; break; } } @@ -171,13 +163,13 @@ inline constexpr auto identifier_map = make_identifier_map(); // Returns the identifier of the first enumerator of E equal to value or an // empty string view if there is no such enumerator. -template constexpr auto identifier_of(E value) -> string_view { +template constexpr auto identifier_of(E value) -> std::string_view { constexpr size_t table_size = identifier_table_size(); if constexpr (table_size != 0) { // Values outside of the table wrap around and are rejected by the check. auto i = to_uint64(value) - to_uint64(min_enumerator()); return i < table_size ? identifier_table[static_cast(i)] - : string_view(); + : std::string_view(); } else { constexpr size_t map_size = identifier_map_size(); // A free slot terminates the search and its empty identifier is the result. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ce1c1f15..86966198 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -50,11 +50,6 @@ add_fmt_test(format-test mock-allocator.h) if (MSVC) target_compile_options(format-test PRIVATE /bigobj) endif () -if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16) - # format-test completes a type after it failed to be complete in a SFINAE - # context, which is exactly what it is testing. - target_compile_options(format-test PRIVATE -Wno-sfinae-incomplete) -endif () if (NOT (MSVC AND BUILD_SHARED_LIBS)) add_fmt_test(format-impl-test HEADER_ONLY header-only-test.cc) endif ()