Skip to content

Commit df02f20

Browse files
committed
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)
1 parent a3f3f2e commit df02f20

3 files changed

Lines changed: 123 additions & 3 deletions

File tree

include/fmt/base.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,34 @@ enum class uint128_opt {};
428428
template <typename T> auto convert_for_visit(T) -> monostate { return {}; }
429429
#endif
430430

431+
#ifndef FMT_USE_BITINT
432+
# if FMT_CLANG_VERSION >= 1400
433+
# define FMT_USE_BITINT 1
434+
# else
435+
# define FMT_USE_BITINT 0
436+
# endif
437+
#endif
438+
439+
template <class T, int N = 0> struct bitint_traits {};
440+
#if FMT_USE_BITINT
441+
# pragma clang diagnostic push
442+
# pragma clang diagnostic ignored "-Wbit-int-extension"
443+
444+
// fmt only supports up to 128 bits https://github.com/fmtlib/fmt/pull/4072
445+
template <int N> struct bitint_traits<_BitInt(N)> {
446+
static constexpr bool is_signed = true;
447+
static constexpr size_t number_of_bits = N;
448+
static constexpr bool is_formattable = number_of_bits <= 128;
449+
};
450+
template <int N> struct bitint_traits<unsigned _BitInt(N)> {
451+
static constexpr bool is_signed = false;
452+
static constexpr size_t number_of_bits = N;
453+
static constexpr bool is_formattable = number_of_bits <= 128;
454+
};
455+
456+
# pragma clang diagnostic pop
457+
#endif
458+
431459
// Casts a nonnegative integer to unsigned.
432460
template <typename Int>
433461
FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t<Int> {
@@ -1476,6 +1504,17 @@ template <typename Context> struct arg_mapper {
14761504
FMT_MAP_API auto map(double val) -> double { return val; }
14771505
FMT_MAP_API auto map(long double val) -> long double { return val; }
14781506

1507+
template <class T,
1508+
FMT_ENABLE_IF(bitint_traits<remove_cvref_t<T>>::is_formattable)>
1509+
FMT_MAP_API auto map(T&& val) -> decltype(val) {
1510+
return val;
1511+
}
1512+
template <class T,
1513+
FMT_ENABLE_IF(!bitint_traits<remove_cvref_t<T>>::is_formattable)>
1514+
FMT_MAP_API auto map(T&&) -> unformattable {
1515+
return {};
1516+
}
1517+
14791518
FMT_MAP_API auto map(char_type* val) -> const char_type* { return val; }
14801519
FMT_MAP_API auto map(const char_type* val) -> const char_type* { return val; }
14811520
template <typename T, typename Char = char_t<T>,

include/fmt/format.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3950,6 +3950,26 @@ class formatter<std::basic_string<Char, Traits, Allocator>, Char>
39503950
template <typename Char, size_t N>
39513951
struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {};
39523952

3953+
#if FMT_USE_BITINT
3954+
namespace detail {
3955+
template <class T> struct bit_int_format_as {
3956+
using traits = bitint_traits<T>;
3957+
static constexpr size_t N = traits::number_of_bits;
3958+
static constexpr bool is_signed = traits::is_signed;
3959+
using type = conditional_t<
3960+
N <= 64, conditional_t<is_signed, int64_t, uint64_t>,
3961+
conditional_t<(N > 64 && N <= 128),
3962+
conditional_t<is_signed, __int128, unsigned __int128>,
3963+
void>>;
3964+
};
3965+
} // namespace detail
3966+
3967+
template <typename T, typename Char>
3968+
struct formatter<T, Char,
3969+
enable_if_t<(detail::bitint_traits<T>::is_formattable)>>
3970+
: formatter<typename detail::bit_int_format_as<T>::type, Char> {};
3971+
#endif
3972+
39533973
/**
39543974
* Converts `p` to `const void*` for pointer formatting.
39553975
*

test/format-test.cc

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@
3030
# include <version>
3131
#endif
3232

33+
#include <limits.h>
34+
35+
#include <limits>
36+
3337
#include "gtest-extra.h"
3438
#include "mock-allocator.h"
3539
#include "util.h"
36-
3740
using fmt::basic_memory_buffer;
3841
using fmt::format_error;
3942
using fmt::memory_buffer;
@@ -931,8 +934,7 @@ TEST(format_test, runtime_width) {
931934
}
932935

933936
TEST(format_test, exponent_range) {
934-
for (int e = -1074; e <= 1023; ++e)
935-
(void)fmt::format("{}", std::ldexp(1, e));
937+
for (int e = -1074; e <= 1023; ++e) (void)fmt::format("{}", std::ldexp(1, e));
936938
}
937939

938940
TEST(format_test, precision) {
@@ -2507,3 +2509,62 @@ TEST(format_test, writer) {
25072509
fmt::writer(s).print("foo");
25082510
EXPECT_EQ(s.str(), "foo");
25092511
}
2512+
2513+
#if FMT_USE_BITINT
2514+
# pragma clang diagnostic ignored "-Wbit-int-extension"
2515+
2516+
template <size_t N, bool is_signed>
2517+
using bitint_helper =
2518+
fmt::conditional_t<is_signed, _BitInt(N), unsigned _BitInt(N)>;
2519+
template <size_t N> using signed_bitint = bitint_helper<N, true>;
2520+
template <size_t N> using unsigned_bitint = bitint_helper<N, false>;
2521+
2522+
TEST(format_test, bitint) {
2523+
EXPECT_EQ(fmt::format("{}", unsigned_bitint<3>(7)), "7");
2524+
EXPECT_EQ(fmt::format("{}", signed_bitint<7>()), "0");
2525+
2526+
EXPECT_EQ(fmt::format("{}", unsigned_bitint<15>(31000)), "31000");
2527+
EXPECT_EQ(fmt::format("{}", signed_bitint<16>(INT16_MIN)), "-32768");
2528+
EXPECT_EQ(fmt::format("{}", signed_bitint<16>(INT16_MAX)), "32767");
2529+
2530+
EXPECT_EQ(fmt::format("{}", unsigned_bitint<32>(4294967295)), "4294967295");
2531+
2532+
EXPECT_EQ(fmt::format("{}", unsigned_bitint<47>(140737488355327ULL)),
2533+
"140737488355327");
2534+
EXPECT_EQ(fmt::format("{}", signed_bitint<47>(-40737488355327LL)),
2535+
"-40737488355327");
2536+
2537+
// Check lvalues and const
2538+
auto a = signed_bitint<8>(0);
2539+
auto b = unsigned_bitint<32>(4294967295);
2540+
const auto c = signed_bitint<7>(0);
2541+
const auto d = unsigned_bitint<32>(4294967295);
2542+
EXPECT_EQ(fmt::format("{}", a), "0");
2543+
EXPECT_EQ(fmt::format("{}", b), "4294967295");
2544+
EXPECT_EQ(fmt::format("{}", c), "0");
2545+
EXPECT_EQ(fmt::format("{}", d), "4294967295");
2546+
2547+
static_assert(fmt::is_formattable<signed_bitint<64>, char>{}, "");
2548+
static_assert(fmt::is_formattable<unsigned_bitint<64>, char>{}, "");
2549+
2550+
# if FMT_USE_INT128
2551+
static_assert(fmt::is_formattable<signed_bitint<128>, char>{}, "");
2552+
static_assert(fmt::is_formattable<unsigned_bitint<128>, char>{}, "");
2553+
2554+
EXPECT_EQ(fmt::format("{}", signed_bitint<128>(0)), "0");
2555+
EXPECT_EQ(fmt::format("{}", unsigned_bitint<128>(0)), "0");
2556+
EXPECT_EQ("9223372036854775808",
2557+
fmt::format("{}", signed_bitint<65>(INT64_MAX) + 1));
2558+
EXPECT_EQ("-9223372036854775809",
2559+
fmt::format("{}", signed_bitint<65>(INT64_MIN) - 1));
2560+
EXPECT_EQ("18446744073709551616",
2561+
fmt::format("{}", unsigned_bitint<66>(UINT64_MAX) + 1));
2562+
EXPECT_EQ("170141183460469231731687303715884105727",
2563+
fmt::format("{}", signed_bitint<128>(int128_max)));
2564+
EXPECT_EQ("-170141183460469231731687303715884105728",
2565+
fmt::format("{}", signed_bitint<128>(int128_min)));
2566+
EXPECT_EQ("340282366920938463463374607431768211455",
2567+
fmt::format("{}", unsigned_bitint<128>(uint128_max)));
2568+
# endif
2569+
}
2570+
#endif

0 commit comments

Comments
 (0)