Add member format_as for std

This commit is contained in:
Victor Zverovich 2024-09-20 07:08:47 -07:00
parent 6d43c755bc
commit a197a994c5
4 changed files with 38 additions and 19 deletions

View File

@ -17,8 +17,6 @@
# include <stdio.h> // FILE # include <stdio.h> // FILE
# include <string.h> // memcmp # include <string.h> // memcmp
// <cstddef> is also included transitively from <type_traits>.
# include <cstddef> // std::byte
# include <type_traits> // std::enable_if # include <type_traits> // std::enable_if
#endif #endif
@ -309,6 +307,7 @@ using make_unsigned_t = typename std::make_unsigned<T>::type;
template <typename T> template <typename T>
using underlying_t = typename std::underlying_type<T>::type; using underlying_t = typename std::underlying_type<T>::type;
template <typename T> using decay_t = typename std::decay<T>::type; template <typename T> using decay_t = typename std::decay<T>::type;
using nullptr_t = decltype(nullptr);
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 #if FMT_GCC_VERSION && FMT_GCC_VERSION < 500
// A workaround for gcc 4.9 to make void_t work in a SFINAE context. // A workaround for gcc 4.9 to make void_t work in a SFINAE context.
@ -506,7 +505,7 @@ template <typename Char> class basic_string_view {
constexpr basic_string_view(const Char* s, size_t count) noexcept constexpr basic_string_view(const Char* s, size_t count) noexcept
: data_(s), size_(count) {} : data_(s), size_(count) {}
constexpr basic_string_view(std::nullptr_t) = delete; constexpr basic_string_view(nullptr_t) = delete;
/// Constructs a string reference object from a C string. /// Constructs a string reference object from a C string.
#if FMT_GCC_VERSION #if FMT_GCC_VERSION
@ -643,15 +642,6 @@ struct formatter {
formatter() = delete; formatter() = delete;
}; };
// This is defined in base.h instead of format.h to avoid injecting in std.
// It is a template to avoid undesirable implicit conversions to std::byte.
#ifdef __cpp_lib_byte
template <typename T, FMT_ENABLE_IF(std::is_same<T, std::byte>::value)>
inline auto format_as(T b) -> unsigned char {
return static_cast<unsigned char>(b);
}
#endif
/// Reports a format error at compile time or, via a `format_error` exception, /// Reports a format error at compile time or, via a `format_error` exception,
/// at runtime. /// at runtime.
// This function is intentionally not constexpr to give a compile-time error. // This function is intentionally not constexpr to give a compile-time error.
@ -1071,14 +1061,24 @@ using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
template <typename T> template <typename T>
using format_as_result = using format_as_result =
remove_cvref_t<decltype(format_as(std::declval<const T&>()))>; remove_cvref_t<decltype(format_as(std::declval<const T&>()))>;
template <typename T>
using format_as_member_result =
remove_cvref_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>;
template <typename T, typename Enable = std::true_type> template <typename T, typename Enable = std::true_type>
struct use_format_as : std::false_type {}; struct use_format_as : std::false_type {};
// format_as member is only used to avoid injection into the std namespace.
template <typename T, typename Enable = std::true_type>
struct use_format_as_member : std::false_type {};
// Only map owning types because mapping views can be unsafe. // Only map owning types because mapping views can be unsafe.
template <typename T> template <typename T>
struct use_format_as< struct use_format_as<
T, bool_constant<std::is_integral<format_as_result<T>>::value>> T, bool_constant<std::is_arithmetic<format_as_result<T>>::value>>
: std::true_type {};
template <typename T>
struct use_format_as_member<
T, bool_constant<std::is_arithmetic<format_as_member_result<T>>::value>>
: std::true_type {}; : std::true_type {};
template <typename T, typename U = remove_const_t<T>> template <typename T, typename U = remove_const_t<T>>
@ -1086,7 +1086,7 @@ using use_formatter =
bool_constant<(std::is_class<T>::value || std::is_enum<T>::value || bool_constant<(std::is_class<T>::value || std::is_enum<T>::value ||
std::is_union<T>::value || std::is_array<T>::value) && std::is_union<T>::value || std::is_array<T>::value) &&
!has_to_string_view<T>::value && !is_named_arg<T>::value && !has_to_string_view<T>::value && !is_named_arg<T>::value &&
!use_format_as<T>::value>; !use_format_as<T>::value && !use_format_as_member<T>::value>;
template <typename Char, typename T, typename U = remove_const_t<T>> template <typename Char, typename T, typename U = remove_const_t<T>>
auto has_formatter_impl(T* p, buffered_context<Char>* ctx = nullptr) auto has_formatter_impl(T* p, buffered_context<Char>* ctx = nullptr)
@ -1140,13 +1140,15 @@ template <typename Char> struct type_mapper {
static auto map(const void*) -> const void*; static auto map(const void*) -> const void*;
static auto map(volatile void*) -> const void*; static auto map(volatile void*) -> const void*;
static auto map(const volatile void*) -> const void*; static auto map(const volatile void*) -> const void*;
static auto map(std::nullptr_t) -> const void*; static auto map(nullptr_t) -> const void*;
template <typename T, FMT_ENABLE_IF(std::is_pointer<T>::value || template <typename T, FMT_ENABLE_IF(std::is_pointer<T>::value ||
std::is_member_pointer<T>::value)> std::is_member_pointer<T>::value)>
static auto map(const T&) -> void; static auto map(const T&) -> void;
template <typename T, FMT_ENABLE_IF(use_format_as<T>::value)> template <typename T, FMT_ENABLE_IF(use_format_as<T>::value)>
static auto map(const T& x) -> decltype(map(format_as(x))); static auto map(const T& x) -> decltype(map(format_as(x)));
template <typename T, FMT_ENABLE_IF(use_format_as_member<T>::value)>
static auto map(const T& x) -> decltype(map(formatter<T>::format_as(x)));
template <typename T, FMT_ENABLE_IF(use_formatter<T>::value)> template <typename T, FMT_ENABLE_IF(use_formatter<T>::value)>
static auto map(T&) -> conditional_t<has_formatter<T, Char>(), T&, void>; static auto map(T&) -> conditional_t<has_formatter<T, Char>(), T&, void>;
@ -2130,7 +2132,7 @@ template <typename Context> class value {
: pointer(const_cast<const void*>(x)) {} : pointer(const_cast<const void*>(x)) {}
FMT_INLINE value(const volatile void* x FMT_BUILTIN) FMT_INLINE value(const volatile void* x FMT_BUILTIN)
: pointer(const_cast<const void*>(x)) {} : pointer(const_cast<const void*>(x)) {}
FMT_INLINE value(std::nullptr_t) : pointer(nullptr) {} FMT_INLINE value(nullptr_t) : pointer(nullptr) {}
template <typename T, FMT_ENABLE_IF(std::is_pointer<T>::value || template <typename T, FMT_ENABLE_IF(std::is_pointer<T>::value ||
std::is_member_pointer<T>::value)> std::is_member_pointer<T>::value)>
@ -2144,6 +2146,8 @@ template <typename Context> class value {
template <typename T, FMT_ENABLE_IF(use_format_as<T>::value)> template <typename T, FMT_ENABLE_IF(use_format_as<T>::value)>
value(const T& x) : value(format_as(x)) {} value(const T& x) : value(format_as(x)) {}
template <typename T, FMT_ENABLE_IF(use_format_as_member<T>::value)>
value(const T& x) : value(formatter<T>::format_as(x)) {}
template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)> template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
value(const T& named_arg) : value(named_arg.value) {} value(const T& named_arg) : value(named_arg.value) {}
@ -2376,9 +2380,9 @@ template <typename T> class basic_appender {
public: public:
using iterator_category = int; using iterator_category = int;
using value_type = T; using value_type = T;
using difference_type = ptrdiff_t;
using pointer = T*; using pointer = T*;
using reference = T&; using reference = T&;
using difference_type = decltype(pointer() - pointer());
using container_type = detail::buffer<T>; using container_type = detail::buffer<T>;
FMT_CONSTEXPR basic_appender(detail::buffer<T>& buf) : container(&buf) {} FMT_CONSTEXPR basic_appender(detail::buffer<T>& buf) : container(&buf) {}

View File

@ -42,6 +42,7 @@
#ifndef FMT_MODULE #ifndef FMT_MODULE
# include <cmath> // std::signbit # include <cmath> // std::signbit
# include <cstddef> // std::byte
# include <cstdint> // uint32_t # include <cstdint> // uint32_t
# include <cstring> // std::memcpy # include <cstring> // std::memcpy
# include <initializer_list> // std::initializer_list # include <initializer_list> // std::initializer_list
@ -3919,6 +3920,18 @@ constexpr auto format_as(Enum e) noexcept -> underlying_t<Enum> {
} }
} // namespace enums } // namespace enums
#ifdef __cpp_lib_byte
template <> struct formatter<std::byte> : formatter<unsigned> {
static auto format_as(std::byte b) -> unsigned char {
return static_cast<unsigned char>(b);
}
template <typename Context>
auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) {
return formatter<unsigned>::format(format_as(b), ctx);
}
};
#endif
class bytes { class bytes {
private: private:
string_view data_; string_view data_;

View File

@ -115,8 +115,8 @@ struct is_range_<T, void>
// tuple_size and tuple_element check. // tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ { template <typename T> class is_tuple_like_ {
template <typename U> template <typename U, typename V = typename std::remove_cv<U>::type>
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int()); static auto check(U* p) -> decltype(std::tuple_size<V>::value, 0);
template <typename> static void check(...); template <typename> static void check(...);
public: public:

View File

@ -2030,6 +2030,8 @@ enum big_enum : unsigned long long { big_enum_value = 5000000000ULL };
auto format_as(big_enum e) -> unsigned long long { return e; } auto format_as(big_enum e) -> unsigned long long { return e; }
TEST(format_test, strong_enum) { TEST(format_test, strong_enum) {
auto arg = fmt::basic_format_arg<fmt::context>(big_enum_value);
EXPECT_EQ(arg.type(), fmt::detail::type::ulong_long_type);
EXPECT_EQ(fmt::format("{}", big_enum_value), "5000000000"); EXPECT_EQ(fmt::format("{}", big_enum_value), "5000000000");
} }
#endif #endif