diff --git a/format.cc b/format.cc index c040edd1..519b072a 100644 --- a/format.cc +++ b/format.cc @@ -207,16 +207,6 @@ class PrecisionHandler : } }; -// MakeUnsigned::Type gives an unsigned type corresponding to integer type T. -template -struct MakeUnsigned { typedef T Type; }; - -template <> -struct MakeUnsigned { typedef unsigned char Type; }; - -template <> -struct MakeUnsigned { typedef unsigned short Type; }; - // Converts an integer argument to type T. template class ArgConverter : public fmt::internal::ArgVisitor, void> { @@ -229,12 +219,26 @@ class ArgConverter : public fmt::internal::ArgVisitor, void> { template void visit_any_int(U value) { - if (type_ == 'd' || type_ == 'i') { - arg_.type = fmt::internal::Arg::INT; - arg_.int_value = static_cast(value); + bool is_signed = type_ == 'd' || type_ == 'i'; + using fmt::internal::Arg; + if (sizeof(T) <= sizeof(int)) { + if (is_signed) { + arg_.type = Arg::INT; + arg_.int_value = static_cast(value); + } else { + arg_.type = Arg::UINT; + arg_.uint_value = + static_cast::Type>(value); + } } else { - arg_.type = fmt::internal::Arg::UINT; - arg_.uint_value = static_cast::Type>(value); + if (is_signed) { + arg_.type = Arg::LONG_LONG; + arg_.long_long_value = static_cast(value); + } else { + arg_.type = Arg::ULONG_LONG; + arg_.ulong_long_value = + static_cast::Type>(value); + } } } }; @@ -902,6 +906,10 @@ void fmt::internal::PrintfFormatter::format( } // Parse length and convert the argument to the required type. + // Conversion is done for compatibility with glibc's printf, MSVC's + // printf simply ignores width specifiers. For example: + // printf("%hhd", -129); + // prints 127 when using glibc's printf and -129 when using MSVC's one. switch (*s) { case 'h': { ++s; @@ -912,6 +920,9 @@ void fmt::internal::PrintfFormatter::format( break; } case 'l': + ++s; + ArgConverter(arg, *s).visit(arg); + break; case 'j': case 'z': case 't': diff --git a/format.h b/format.h index bfaa1249..9be7319e 100644 --- a/format.h +++ b/format.h @@ -420,6 +420,18 @@ struct IntTraits { TypeSelector::digits <= 32>::Type MainType; }; +// MakeUnsigned::Type gives an unsigned type corresponding to integer type T. +template +struct MakeUnsigned { typedef T Type; }; + +#define SPECIALIZE_MAKE_UNSIGNED(T) \ + template <> \ + struct MakeUnsigned { typedef unsigned T Type; } + +SPECIALIZE_MAKE_UNSIGNED(char); +SPECIALIZE_MAKE_UNSIGNED(short); +SPECIALIZE_MAKE_UNSIGNED(long); + template struct IsLongDouble { enum {VALUE = 0}; }; diff --git a/test/printf-test.cc b/test/printf-test.cc index cc86a1da..95db8094 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -273,10 +273,27 @@ TEST(PrintfTest, DynamicPrecision) { } } -#define EXPECT_STD_PRINTF(format, Type, arg) { \ +// Cast value to T to workaround an issue with MSVC not implementing +// various format specifiers. +template +T Cast(U value) { return value; } + +bool IsSupported(const std::string &format) { +#ifdef _MSVC + // MSVC doesn't support hh, j, z and t format specifiers. + return format != "hh"; +#else + return true; +#endif +} + +#define EXPECT_STD_PRINTF(format, T, arg) { \ char buffer[BUFFER_SIZE]; \ - Type conv_arg = static_cast(arg); \ - safe_sprintf(buffer, fmt::StringRef(format).c_str(), conv_arg); \ + if (IsSupported(format)) \ + safe_sprintf(buffer, fmt::StringRef(format).c_str(), arg); \ + else \ + safe_sprintf(buffer, fmt::StringRef(format).c_str(), \ + Cast::Type>(arg)); \ EXPECT_PRINTF(buffer, format, arg); \ }