Skip to content

Commit

Permalink
Add support for _BitInt on clang
Browse files Browse the repository at this point in the history
Issue fmtlib#4007
Make _BitInt up to 128bits formattable
Note, libstdc++ is_signed doesn't work with _BitInt (so use own)
  • Loading branch information
Arghnews committed Aug 26, 2024
1 parent 0379bf3 commit 7322e2c
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 2 deletions.
43 changes: 43 additions & 0 deletions include/fmt/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,33 @@ enum class uint128_opt {};
template <typename T> 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 <class T, int N = 0> 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 <int N> 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 <int N> struct bitint_traits<unsigned _BitInt(N)> : 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 <typename Int>
FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t<Int> {
Expand Down Expand Up @@ -1475,6 +1502,19 @@ template <typename Context> 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 <class T,
FMT_ENABLE_IF(bitint_traits<remove_cvref_t<T>>::is_formattable)>
FMT_MAP_API auto map(T&& val) -> decltype(val) {
return val;
}
template <class T,
FMT_ENABLE_IF(!bitint_traits<remove_cvref_t<T>>::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 <typename T, typename Char = char_t<T>,
Expand Down Expand Up @@ -3157,6 +3197,9 @@ FMT_INLINE void println(format_string<T...> fmt, T&&... args) {
}

FMT_END_EXPORT
#if FMT_USE_BITINT
# pragma clang diagnostic pop
#endif
FMT_GCC_PRAGMA("GCC pop_options")
FMT_END_NAMESPACE

Expand Down
20 changes: 20 additions & 0 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -3950,6 +3950,26 @@ class formatter<std::basic_string<Char, Traits, Allocator>, Char>
template <typename Char, size_t N>
struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {};

#if FMT_USE_BITINT
namespace detail {
template <class T> struct bit_int_format_as {
using traits = bitint_traits<T>;
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<is_signed, int64_t, uint64_t>,
conditional_t<(N > 64 && N <= 128),
conditional_t<is_signed, __int128, unsigned __int128>,
void>>;
};
} // namespace detail

template <typename T, typename Char>
struct formatter<T, Char,
enable_if_t<(detail::bitint_traits<T>::is_formattable)>>
: formatter<typename detail::bit_int_format_as<T>::type, Char> {};
#endif

/**
* Converts `p` to `const void*` for pointer formatting.
*
Expand Down
69 changes: 67 additions & 2 deletions test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 <size_t N, bool is_signed>
using bitint_helper =
fmt::conditional_t<is_signed, _BitInt(N), unsigned _BitInt(N)>;
template <size_t N> using signed_bitint = bitint_helper<N, true>;
template <size_t N> using unsigned_bitint = bitint_helper<N, false>;

# 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<signed_bitint<64>, char>{}, "");
static_assert(fmt::is_formattable<unsigned_bitint<64>, char>{}, "");

static_assert(!fmt::is_formattable<signed_bitint<129>, char>{}, "");
static_assert(!fmt::is_formattable<unsigned_bitint<129>, char>{}, "");
# if FMT_USE_INT128
static_assert(fmt::is_formattable<signed_bitint<128>, char>{}, "");
static_assert(fmt::is_formattable<unsigned_bitint<128>, 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

0 comments on commit 7322e2c

Please sign in to comment.