Allow %s as generic format specifier in printf (#453)

* Allow %s as generic format specifier in printf

Signed integers are formatted as %d
Unsigned integers are formatted as %u
Doubles are formatted as %f
Chars are formatted as %c
Void Pointers are formatted as %p

* Remove '%S' handling and use visitor for generic format strings

* Default for floating point is now "%g" rather than "%f"
This commit is contained in:
mojoBrendan 2017-01-15 05:45:48 +10:00 committed by Victor Zverovich
parent e0251fdcef
commit d8754af063
2 changed files with 43 additions and 0 deletions

View File

@ -61,6 +61,24 @@ class IsZeroInt : public ArgVisitor<IsZeroInt, bool> {
bool visit_any_int(T value) { return value == 0; }
};
// returns the default type for format specific "%s"
class DefaultType : public ArgVisitor<DefaultType, wchar_t> {
public:
wchar_t visit_char(int) { return 'c'; }
wchar_t visit_bool(bool) { return 's'; }
wchar_t visit_pointer(const void *) { return 'p'; }
template <typename T>
wchar_t visit_any_int(T) { return 'd'; }
template <typename T>
wchar_t visit_any_double(T) { return 'g'; }
wchar_t visit_unhandled_arg() { return 's'; }
};
template <typename T, typename U>
struct is_same {
enum { value = 0 };
@ -92,9 +110,18 @@ class ArgConverter : public ArgVisitor<ArgConverter<T>, void> {
visit_any_int(value);
}
void visit_char(char value) {
if (type_ != 's')
visit_any_int(value);
}
template <typename U>
void visit_any_int(U value) {
bool is_signed = type_ == 'd' || type_ == 'i';
if (type_ == 's') {
is_signed = std::numeric_limits<U>::is_signed;
}
using internal::Arg;
typedef typename internal::Conditional<
is_same<T, void>::value, U, T>::type TargetType;
@ -463,6 +490,12 @@ void PrintfFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {
if (!*s)
FMT_THROW(FormatError("invalid format string"));
spec.type_ = static_cast<char>(*s++);
if (spec.type_ == 's') {
// set the format type to the default if 's' is specified
spec.type_ = internal::DefaultType().visit(arg);
}
if (arg.type <= Arg::LAST_INTEGER_TYPE) {
// Normalize type.
switch (spec.type_) {

View File

@ -202,6 +202,8 @@ TEST(PrintfTest, HashFlag) {
TEST(PrintfTest, Width) {
EXPECT_PRINTF(" abc", "%5s", "abc");
EXPECT_PRINTF(" -42", "%5s", "-42");
EXPECT_PRINTF(" 0.123456", "%10s", 0.123456);
// Width cannot be specified twice.
EXPECT_THROW_MSG(fmt::sprintf("%5-5d", 42), FormatError,
@ -381,11 +383,13 @@ TEST(PrintfTest, Bool) {
TEST(PrintfTest, Int) {
EXPECT_PRINTF("-42", "%d", -42);
EXPECT_PRINTF("-42", "%i", -42);
EXPECT_PRINTF("-42", "%s", -42);
unsigned u = 0 - 42u;
EXPECT_PRINTF(fmt::format("{}", u), "%u", -42);
EXPECT_PRINTF(fmt::format("{:o}", u), "%o", -42);
EXPECT_PRINTF(fmt::format("{:x}", u), "%x", -42);
EXPECT_PRINTF(fmt::format("{:X}", u), "%X", -42);
EXPECT_PRINTF(fmt::format("{}", u), "%s", u);
}
TEST(PrintfTest, LongLong) {
@ -398,6 +402,7 @@ TEST(PrintfTest, LongLong) {
TEST(PrintfTest, Float) {
EXPECT_PRINTF("392.650000", "%f", 392.65);
EXPECT_PRINTF("392.650000", "%F", 392.65);
EXPECT_PRINTF("392.65", "%s", 392.65);
char buffer[BUFFER_SIZE];
safe_sprintf(buffer, "%e", 392.65);
EXPECT_PRINTF(buffer, "%e", 392.65);
@ -422,6 +427,7 @@ TEST(PrintfTest, Inf) {
TEST(PrintfTest, Char) {
EXPECT_PRINTF("x", "%c", 'x');
EXPECT_PRINTF("x", "%s", 'x');
int max = std::numeric_limits<int>::max();
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
//EXPECT_PRINTF("x", "%lc", L'x');
@ -440,13 +446,17 @@ TEST(PrintfTest, Pointer) {
int n;
void *p = &n;
EXPECT_PRINTF(fmt::format("{}", p), "%p", p);
EXPECT_PRINTF(fmt::format("{}", p), "%s", p);
p = 0;
EXPECT_PRINTF("(nil)", "%p", p);
EXPECT_PRINTF(" (nil)", "%10p", p);
EXPECT_PRINTF("(nil)", "%s", p);
EXPECT_PRINTF(" (nil)", "%10s", p);
const char *s = "test";
EXPECT_PRINTF(fmt::format("{:p}", s), "%p", s);
const char *null_str = 0;
EXPECT_PRINTF("(nil)", "%p", null_str);
EXPECT_PRINTF("(null)", "%s", null_str);
}
TEST(PrintfTest, Location) {