mirror of
https://github.com/fmtlib/fmt.git
synced 2026-09-13 14:22:26 +08:00
Simplify enum identifier handling and tidy reflection docs
Use std::string_view directly for enumerator identifiers, dropping the identifier wrapper and inlining count_enumerators. Update the fmt/enum.h documentation to point at cppreference and drop an inaccurate note. Also remove the no-longer-needed -Wno-sfinae-incomplete option for format-test.
This commit is contained in:
parent
8a84280f26
commit
9375eb3792
17
doc/api.md
17
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 `<meta>` 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.
|
||||
|
||||
<a id="compile-api"></a>
|
||||
## Compile-Time Support
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#if FMT_USE_REFLECTION && !defined(FMT_MODULE)
|
||||
# include <array>
|
||||
# include <meta>
|
||||
# include <string_view>
|
||||
# include <utility> // 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 <typename E> constexpr auto to_uint64(E value) -> uint64_t {
|
||||
return static_cast<uint64_t>(static_cast<underlying_t<E>>(value));
|
||||
}
|
||||
|
||||
template <typename E> 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 <typename E> consteval auto min_enumerator() -> E {
|
||||
auto enumerators = std::meta::enumerators_of(^^E);
|
||||
@ -108,13 +98,15 @@ template <typename E> consteval auto identifier_table_size() -> size_t {
|
||||
}
|
||||
|
||||
template <typename E, size_t N = identifier_table_size<E>()>
|
||||
consteval auto make_identifier_table() -> std::array<string_view, N> {
|
||||
auto ids = std::array<string_view, N>();
|
||||
consteval auto make_identifier_table() -> std::array<std::string_view, N> {
|
||||
auto ids = std::array<std::string_view, N>();
|
||||
auto min = to_uint64(min_enumerator<E>());
|
||||
for (std::meta::info e : std::meta::enumerators_of(^^E)) {
|
||||
auto i = static_cast<size_t>(to_uint64(std::meta::extract<E>(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<E>();
|
||||
// below 0.5, which guarantees a free slot and therefore terminates the search.
|
||||
template <typename E> consteval auto identifier_map_size() -> size_t {
|
||||
auto size = size_t(1);
|
||||
while (size < count_enumerators<E>() * 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 <typename E, size_t N = identifier_map_size<E>()>
|
||||
consteval auto make_identifier_map()
|
||||
-> std::array<std::pair<E, string_view>, N> {
|
||||
auto map = std::array<std::pair<E, string_view>, N>();
|
||||
-> std::array<std::pair<E, std::string_view>, N> {
|
||||
auto map = std::array<std::pair<E, std::string_view>, N>();
|
||||
for (std::meta::info e : std::meta::enumerators_of(^^E)) {
|
||||
auto value = std::meta::extract<E>(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<E>();
|
||||
|
||||
// Returns the identifier of the first enumerator of E equal to value or an
|
||||
// empty string view if there is no such enumerator.
|
||||
template <typename E> constexpr auto identifier_of(E value) -> string_view {
|
||||
template <typename E> constexpr auto identifier_of(E value) -> std::string_view {
|
||||
constexpr size_t table_size = identifier_table_size<E>();
|
||||
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<E>());
|
||||
return i < table_size ? identifier_table<E>[static_cast<size_t>(i)]
|
||||
: string_view();
|
||||
: std::string_view();
|
||||
} else {
|
||||
constexpr size_t map_size = identifier_map_size<E>();
|
||||
// A free slot terminates the search and its empty identifier is the result.
|
||||
|
||||
@ -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 ()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user