From 7322e2c17d46c1aac4cc31028e262d4852fcc6e6 Mon Sep 17 00:00:00 2001 From: Justin Riddell Date: Mon, 26 Aug 2024 14:36:37 +0100 Subject: [PATCH] Add support for _BitInt on clang Issue #4007 Make _BitInt up to 128bits formattable Note, libstdc++ is_signed doesn't work with _BitInt (so use own) --- include/fmt/base.h | 43 +++++++++++++++++++++++++++ include/fmt/format.h | 20 +++++++++++++ test/format-test.cc | 69 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index 671c2ec7ec5f7..08192ecf8ff70 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -427,6 +427,33 @@ enum class uint128_opt {}; template auto convert_for_visit(T) -> monostate { return {}; } #endif +#ifndef FMT_USE_BITINT +# if FMT_CLANG_VERSION >= 1400 +# define FMT_USE_BITINT 1 +# else +# define FMT_USE_BITINT 0 +# endif +#endif + +template struct bitint_traits : std::false_type {}; +#if FMT_USE_BITINT +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wbit-int-extension" + +// fmt only supports up to 128 bits https://github.com/fmtlib/fmt/pull/4072 +template struct bitint_traits<_BitInt(N)> : std::true_type { + static constexpr bool is_signed = true; + static constexpr size_t number_of_bits = N; + static constexpr bool is_formattable = number_of_bits <= 128; +}; +template struct bitint_traits : std::true_type { + static constexpr bool is_signed = false; + static constexpr size_t number_of_bits = N; + static constexpr bool is_formattable = number_of_bits <= 128; +}; + +#endif + // Casts a nonnegative integer to unsigned. template FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { @@ -1475,6 +1502,19 @@ template struct arg_mapper { FMT_MAP_API auto map(double val) -> double { return val; } FMT_MAP_API auto map(long double val) -> long double { return val; } +#if FMT_USE_BITINT + template >::is_formattable)> + FMT_MAP_API auto map(T&& val) -> decltype(val) { + return val; + } + template >::is_formattable)> + FMT_MAP_API auto map(T&&) -> unformattable { + return {}; + } +#endif + FMT_MAP_API auto map(char_type* val) -> const char_type* { return val; } FMT_MAP_API auto map(const char_type* val) -> const char_type* { return val; } template , @@ -3157,6 +3197,9 @@ FMT_INLINE void println(format_string fmt, T&&... args) { } FMT_END_EXPORT +#if FMT_USE_BITINT +# pragma clang diagnostic pop +#endif FMT_GCC_PRAGMA("GCC pop_options") FMT_END_NAMESPACE diff --git a/include/fmt/format.h b/include/fmt/format.h index f0555011b74c3..eb2a4691d883d 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3950,6 +3950,26 @@ class formatter, Char> template struct formatter : formatter, Char> {}; +#if FMT_USE_BITINT +namespace detail { +template struct bit_int_format_as { + using traits = bitint_traits; + static constexpr size_t N = traits::number_of_bits; + static constexpr bool is_signed = traits::is_signed; + using type = conditional_t< + N <= 64, conditional_t, + conditional_t<(N > 64 && N <= 128), + conditional_t, + void>>; +}; +} // namespace detail + +template +struct formatter::is_formattable)>> + : formatter::type, Char> {}; +#endif + /** * Converts `p` to `const void*` for pointer formatting. * diff --git a/test/format-test.cc b/test/format-test.cc index 64570b7831dcb..9d7d08cf6dd0c 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -931,8 +931,7 @@ TEST(format_test, runtime_width) { } TEST(format_test, exponent_range) { - for (int e = -1074; e <= 1023; ++e) - (void)fmt::format("{}", std::ldexp(1, e)); + for (int e = -1074; e <= 1023; ++e) (void)fmt::format("{}", std::ldexp(1, e)); } TEST(format_test, precision) { @@ -2507,3 +2506,69 @@ TEST(format_test, writer) { fmt::writer(s).print("foo"); EXPECT_EQ(s.str(), "foo"); } + +#if FMT_USE_BITINT +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wbit-int-extension" + +template +using bitint_helper = + fmt::conditional_t; +template using signed_bitint = bitint_helper; +template using unsigned_bitint = bitint_helper; + +# if FMT_USE_BITINT +TEST(format_test, bitint) { + EXPECT_EQ(fmt::format("{}", unsigned_bitint<3>{7}), "7"); + EXPECT_EQ(fmt::format("{}", signed_bitint<7>{}), "0"); + + EXPECT_EQ(fmt::format("{}", unsigned_bitint<15>{31000}), "31000"); + EXPECT_EQ(fmt::format("{}", signed_bitint<16>{INT16_MIN}), "-32768"); + EXPECT_EQ(fmt::format("{}", signed_bitint<16>{INT16_MAX}), "32767"); + + EXPECT_EQ(fmt::format("{}", unsigned_bitint<32>{4294967295}), "4294967295"); + + EXPECT_EQ(fmt::format("{}", unsigned_bitint<47>{140737488355327ULL}), + "140737488355327"); + EXPECT_EQ(fmt::format("{}", signed_bitint<47>{-40737488355327LL}), + "-40737488355327"); + + // Check lvalues and const + auto a = signed_bitint<8>{0}; + auto b = unsigned_bitint<32>{4294967295}; + const auto c = signed_bitint<7>{0}; + const auto d = unsigned_bitint<32>{4294967295}; + EXPECT_EQ(fmt::format("{}", a), "0"); + EXPECT_EQ(fmt::format("{}", b), "4294967295"); + EXPECT_EQ(fmt::format("{}", c), "0"); + EXPECT_EQ(fmt::format("{}", d), "4294967295"); + + static_assert(fmt::is_formattable, char>{}, ""); + static_assert(fmt::is_formattable, char>{}, ""); + + static_assert(!fmt::is_formattable, char>{}, ""); + static_assert(!fmt::is_formattable, char>{}, ""); +# if FMT_USE_INT128 + static_assert(fmt::is_formattable, char>{}, ""); + static_assert(fmt::is_formattable, char>{}, ""); + + EXPECT_EQ(fmt::format("{}", signed_bitint<128>(0)), "0"); + EXPECT_EQ(fmt::format("{}", unsigned_bitint<128>(0)), "0"); + EXPECT_EQ("9223372036854775808", + fmt::format("{}", signed_bitint<65>(INT64_MAX) + 1)); + EXPECT_EQ("-9223372036854775809", + fmt::format("{}", signed_bitint<65>(INT64_MIN) - 1)); + EXPECT_EQ("18446744073709551616", + fmt::format("{}", unsigned_bitint<66>(UINT64_MAX) + 1)); + EXPECT_EQ("170141183460469231731687303715884105727", + fmt::format("{}", signed_bitint<128>(int128_max))); + EXPECT_EQ("-170141183460469231731687303715884105728", + fmt::format("{}", signed_bitint<128>(int128_min))); + EXPECT_EQ("340282366920938463463374607431768211455", + fmt::format("{}", unsigned_bitint<128>(uint128_max))); +# endif +} +# pragma clang diagnostic pop +# endif + +#endif