mirror of
https://github.com/fmtlib/fmt.git
synced 2026-10-28 18:07:37 +08:00
Format enums annotated with fmt::as_underlying
Format an enum as its underlying value if the enum is annotated with
fmt::as_underlying, the equivalent of std::format_as_underlying from P4267:
enum class [[=fmt::as_underlying]] color { red = 1, green = 2, blue = 4 };
fmt::format("{:04x}", color::blue); // "0004"
The value is mapped before type erasure via the existing
formatter<T>::format_as hook, as in the std::byte formatter, so it is stored
as a builtin argument and can be used as dynamic width or precision.
Combining the annotation with fmt::as_identifiers is rejected with a
static_assert.
This commit is contained in:
parent
522e2c12ab
commit
0482001875
16
doc/api.md
16
doc/api.md
@ -584,7 +584,21 @@ as its underlying value in decimal before applying string formatting:
|
||||
fmt::print("{}", static_cast<color>(42));
|
||||
// Output: 42
|
||||
|
||||
Enums without the annotation are not affected and are formatted as before, i.e.
|
||||
An enum annotated with `fmt::as_underlying` is instead formatted as its
|
||||
underlying value using the integer [Format Specification](
|
||||
syntax.md#format-specification):
|
||||
|
||||
enum class [[=fmt::as_underlying]] color { red = 1, green = 2, blue = 4 };
|
||||
|
||||
fmt::print("{:04x}", color::blue);
|
||||
// Output: 0004
|
||||
|
||||
Such an enum is mapped to its underlying type before type erasure, so it can
|
||||
also be used as dynamic width or precision. Unlike `fmt::as_identifiers`, it
|
||||
works with all character types. The two annotations specify different
|
||||
representations and cannot be combined.
|
||||
|
||||
Enums without an 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).
|
||||
|
||||
|
||||
@ -33,7 +33,6 @@ FMT_BEGIN_NAMESPACE
|
||||
|
||||
#if FMT_USE_REFLECTION
|
||||
|
||||
/// The type of the `fmt::as_identifiers` annotation.
|
||||
FMT_EXPORT struct as_identifiers_t {};
|
||||
|
||||
/**
|
||||
@ -49,19 +48,56 @@ FMT_EXPORT struct as_identifiers_t {};
|
||||
*/
|
||||
FMT_EXPORT inline constexpr auto as_identifiers = as_identifiers_t();
|
||||
|
||||
FMT_EXPORT struct as_underlying_t {};
|
||||
|
||||
/**
|
||||
* An annotation that makes an enum format as its underlying value.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* enum class [[=fmt::as_underlying]] color { red = 1, green = 2 };
|
||||
* auto s = fmt::format("{:04x}", color::green); // s == "0002"
|
||||
*/
|
||||
FMT_EXPORT inline constexpr auto as_underlying = as_underlying_t();
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Returns true if U is annotated with an annotation of type A.
|
||||
template <typename A, typename U> consteval auto is_annotated() -> bool {
|
||||
return !std::meta::annotations_of_with_type(^^U, ^^A).empty();
|
||||
}
|
||||
|
||||
// Returns true if T is an enum annotated with fmt::as_identifiers.
|
||||
template <typename T, typename U = remove_cvref_t<T>>
|
||||
consteval auto use_identifiers() -> bool {
|
||||
if constexpr (!std::is_enum<U>::value) {
|
||||
return false;
|
||||
if constexpr (std::is_enum<U>::value) {
|
||||
return is_annotated<as_identifiers_t, U>();
|
||||
} else {
|
||||
return !std::meta::annotations_of_with_type(^^U, ^^as_identifiers_t)
|
||||
.empty();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if T is an enum annotated with fmt::as_underlying.
|
||||
template <typename T, typename U = remove_cvref_t<T>>
|
||||
consteval auto use_underlying() -> bool {
|
||||
if constexpr (std::is_enum<U>::value) {
|
||||
// The annotations specify different representations. Reporting this here
|
||||
// gives a better diagnostic than an ambiguous formatter specialization.
|
||||
static_assert(!is_annotated<as_underlying_t, U>() ||
|
||||
!is_annotated<as_identifiers_t, U>(),
|
||||
"fmt::as_underlying and fmt::as_identifiers are mutually "
|
||||
"exclusive");
|
||||
return is_annotated<as_underlying_t, U>();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The formatter for the underlying type of E. formatter is only specialized
|
||||
// for mapped types, e.g. unsigned rather than unsigned char.
|
||||
template <typename E, typename Char>
|
||||
using underlying_formatter = formatter<mapped_t<underlying_t<E>, Char>, Char>;
|
||||
|
||||
// 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.
|
||||
@ -208,6 +244,22 @@ struct formatter<E, char, enable_if_t<detail::use_identifiers<E>()>> {
|
||||
}
|
||||
};
|
||||
|
||||
// A formatter for enums annotated with fmt::as_underlying.
|
||||
template <typename E, typename Char>
|
||||
struct formatter<E, Char, enable_if_t<detail::use_underlying<E>()>>
|
||||
: detail::underlying_formatter<E, Char> {
|
||||
FMT_CONSTEXPR static auto format_as(E value) noexcept -> underlying_t<E> {
|
||||
return underlying(value);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(E value, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
using base = detail::underlying_formatter<E, Char>;
|
||||
return base::format(format_as(value), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // FMT_USE_REFLECTION
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
@ -73,6 +73,10 @@ enum class [[=fmt::as_identifiers]] extremes : int {
|
||||
enum class [[=fmt::as_identifiers]] big : unsigned long long {
|
||||
huge = ULLONG_MAX
|
||||
};
|
||||
|
||||
enum class [[=fmt::as_underlying]] flags { red = 1, green = 2, blue = 4 };
|
||||
enum class [[=fmt::as_underlying]] small_flags : unsigned char { one = 1 };
|
||||
enum class [[=fmt::as_underlying]] size : int { large = 10 };
|
||||
// clang-format on
|
||||
|
||||
TEST(enum_test, format_enum) {
|
||||
@ -246,4 +250,27 @@ TEST(enum_test, annotation_is_required) {
|
||||
EXPECT_TRUE(fmt::is_formattable<color>::value);
|
||||
EXPECT_FALSE(fmt::is_formattable<color_without_annotation>::value);
|
||||
}
|
||||
|
||||
TEST(enum_test, format_underlying) {
|
||||
EXPECT_EQ(fmt::format("{}", flags::green), "2");
|
||||
// Integer format specifiers are accepted unlike for as_identifiers.
|
||||
EXPECT_EQ(fmt::format("{:04x}", flags::blue), "0004");
|
||||
// A one-byte underlying type is written in decimal, not as a character.
|
||||
EXPECT_EQ(fmt::format("{}", small_flags::one), "1");
|
||||
}
|
||||
|
||||
TEST(enum_test, format_underlying_is_mapped) {
|
||||
// The value is mapped to its underlying type before type erasure.
|
||||
static_assert(fmt::detail::mapped_type_constant<flags, char>::value ==
|
||||
fmt::detail::type::int_type);
|
||||
static_assert(fmt::detail::mapped_type_constant<small_flags, char>::value ==
|
||||
fmt::detail::type::uint_type);
|
||||
// Mapping to an integer allows using an enum as dynamic width.
|
||||
EXPECT_EQ(fmt::format("{:{}}", 42, size::large), " 42");
|
||||
}
|
||||
|
||||
TEST(enum_test, format_underlying_range) {
|
||||
auto v = std::vector<flags>{flags::red, flags::blue};
|
||||
EXPECT_EQ(fmt::format("{}", v), "[1, 4]");
|
||||
}
|
||||
#endif // FMT_USE_REFLECTION
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user