Parameterize BasicFormatter on char type.
This commit is contained in:
parent
dbfd021ae2
commit
687301c516
266
format.cc
266
format.cc
@ -64,32 +64,6 @@ using fmt::StringRef;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct IsLongDouble { enum {VALUE = 0}; };
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct IsLongDouble<long double> { enum {VALUE = 1}; };
|
|
||||||
|
|
||||||
const char DIGITS[] =
|
|
||||||
"0001020304050607080910111213141516171819"
|
|
||||||
"2021222324252627282930313233343536373839"
|
|
||||||
"4041424344454647484950515253545556575859"
|
|
||||||
"6061626364656667686970717273747576777879"
|
|
||||||
"8081828384858687888990919293949596979899";
|
|
||||||
|
|
||||||
// Fills the padding around the content and returns the pointer to the
|
|
||||||
// content area.
|
|
||||||
char *FillPadding(char *buffer,
|
|
||||||
unsigned total_size, std::size_t content_size, char fill) {
|
|
||||||
std::size_t padding = total_size - content_size;
|
|
||||||
std::size_t left_padding = padding / 2;
|
|
||||||
std::fill_n(buffer, left_padding, fill);
|
|
||||||
buffer += left_padding;
|
|
||||||
char *content = buffer;
|
|
||||||
std::fill_n(buffer + content_size, padding - left_padding, fill);
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
int SignBit(double value) {
|
int SignBit(double value) {
|
||||||
if (value < 0) return 1;
|
if (value < 0) return 1;
|
||||||
@ -101,237 +75,23 @@ int SignBit(double value) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicFormatter::FormatDecimal(
|
const char fmt::internal::DIGITS[] =
|
||||||
char *buffer, uint64_t value, unsigned num_digits) {
|
"0001020304050607080910111213141516171819"
|
||||||
--num_digits;
|
"2021222324252627282930313233343536373839"
|
||||||
while (value >= 100) {
|
"4041424344454647484950515253545556575859"
|
||||||
// Integer division is slow so do it for a group of two digits instead
|
"6061626364656667686970717273747576777879"
|
||||||
// of for every digit. The idea comes from the talk by Alexandrescu
|
"8081828384858687888990919293949596979899";
|
||||||
// "Three Optimization Tips for C++". See speed-test for a comparison.
|
|
||||||
unsigned index = (value % 100) * 2;
|
|
||||||
value /= 100;
|
|
||||||
buffer[num_digits] = DIGITS[index + 1];
|
|
||||||
buffer[num_digits - 1] = DIGITS[index];
|
|
||||||
num_digits -= 2;
|
|
||||||
}
|
|
||||||
if (value < 10) {
|
|
||||||
*buffer = static_cast<char>('0' + value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unsigned index = static_cast<unsigned>(value * 2);
|
|
||||||
buffer[1] = DIGITS[index + 1];
|
|
||||||
buffer[0] = DIGITS[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
void BasicFormatter::ReportUnknownType(char code, const char *type) {
|
void fmt::internal::ReportUnknownType(char code, const char *type) {
|
||||||
if (std::isprint(static_cast<unsigned char>(code))) {
|
if (std::isprint(static_cast<unsigned char>(code))) {
|
||||||
throw fmt::FormatError(fmt::str(
|
throw fmt::FormatError(fmt::str(
|
||||||
fmt::Format("unknown format code '{0}' for {1}") << code << type));
|
fmt::Format("unknown format code '{}' for {}") << code << type));
|
||||||
}
|
}
|
||||||
throw fmt::FormatError(
|
throw fmt::FormatError(
|
||||||
fmt::str(fmt::Format("unknown format code '\\x{0:02x}' for {1}")
|
fmt::str(fmt::Format("unknown format code '\\x{:02x}' for {}")
|
||||||
<< static_cast<unsigned>(code) << type));
|
<< static_cast<unsigned>(code) << type));
|
||||||
}
|
}
|
||||||
|
|
||||||
char *BasicFormatter::PrepareFilledBuffer(
|
|
||||||
unsigned size, const AlignSpec &spec, char sign) {
|
|
||||||
unsigned width = spec.width();
|
|
||||||
if (width <= size) {
|
|
||||||
char *p = GrowBuffer(size);
|
|
||||||
*p = sign;
|
|
||||||
return p + size - 1;
|
|
||||||
}
|
|
||||||
char *p = GrowBuffer(width);
|
|
||||||
char *end = p + width;
|
|
||||||
Alignment align = spec.align();
|
|
||||||
if (align == ALIGN_LEFT) {
|
|
||||||
*p = sign;
|
|
||||||
p += size;
|
|
||||||
std::fill(p, end, spec.fill());
|
|
||||||
} else if (align == ALIGN_CENTER) {
|
|
||||||
p = FillPadding(p, width, size, spec.fill());
|
|
||||||
*p = sign;
|
|
||||||
p += size;
|
|
||||||
} else {
|
|
||||||
if (align == ALIGN_NUMERIC) {
|
|
||||||
if (sign) {
|
|
||||||
*p++ = sign;
|
|
||||||
--size;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*(end - size) = sign;
|
|
||||||
}
|
|
||||||
std::fill(p, end - size, spec.fill());
|
|
||||||
p = end;
|
|
||||||
}
|
|
||||||
return p - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void BasicFormatter::FormatDouble(
|
|
||||||
T value, const FormatSpec &spec, int precision) {
|
|
||||||
// Check type.
|
|
||||||
char type = spec.type();
|
|
||||||
bool upper = false;
|
|
||||||
switch (type) {
|
|
||||||
case 0:
|
|
||||||
type = 'g';
|
|
||||||
break;
|
|
||||||
case 'e': case 'f': case 'g':
|
|
||||||
break;
|
|
||||||
case 'F':
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
// MSVC's printf doesn't support 'F'.
|
|
||||||
type = 'f';
|
|
||||||
#endif
|
|
||||||
// Fall through.
|
|
||||||
case 'E': case 'G':
|
|
||||||
upper = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ReportUnknownType(type, "double");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
char sign = 0;
|
|
||||||
// Use SignBit instead of value < 0 because the latter is always
|
|
||||||
// false for NaN.
|
|
||||||
if (SignBit(value)) {
|
|
||||||
sign = '-';
|
|
||||||
value = -value;
|
|
||||||
} else if (spec.sign_flag()) {
|
|
||||||
sign = spec.plus_flag() ? '+' : ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value != value) {
|
|
||||||
// Format NaN ourselves because sprintf's output is not consistent
|
|
||||||
// across platforms.
|
|
||||||
std::size_t size = 4;
|
|
||||||
const char *nan = upper ? " NAN" : " nan";
|
|
||||||
if (!sign) {
|
|
||||||
--size;
|
|
||||||
++nan;
|
|
||||||
}
|
|
||||||
char *out = FormatString(nan, size, spec);
|
|
||||||
if (sign)
|
|
||||||
*out = sign;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isinf(value)) {
|
|
||||||
// Format infinity ourselves because sprintf's output is not consistent
|
|
||||||
// across platforms.
|
|
||||||
std::size_t size = 4;
|
|
||||||
const char *inf = upper ? " INF" : " inf";
|
|
||||||
if (!sign) {
|
|
||||||
--size;
|
|
||||||
++inf;
|
|
||||||
}
|
|
||||||
char *out = FormatString(inf, size, spec);
|
|
||||||
if (sign)
|
|
||||||
*out = sign;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t offset = buffer_.size();
|
|
||||||
unsigned width = spec.width();
|
|
||||||
if (sign) {
|
|
||||||
buffer_.reserve(buffer_.size() + std::max(width, 1u));
|
|
||||||
if (width > 0)
|
|
||||||
--width;
|
|
||||||
++offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build format string.
|
|
||||||
enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg
|
|
||||||
char format[MAX_FORMAT_SIZE];
|
|
||||||
char *format_ptr = format;
|
|
||||||
*format_ptr++ = '%';
|
|
||||||
unsigned width_for_sprintf = width;
|
|
||||||
if (spec.hash_flag())
|
|
||||||
*format_ptr++ = '#';
|
|
||||||
if (spec.align() == ALIGN_CENTER) {
|
|
||||||
width_for_sprintf = 0;
|
|
||||||
} else {
|
|
||||||
if (spec.align() == ALIGN_LEFT)
|
|
||||||
*format_ptr++ = '-';
|
|
||||||
if (width != 0)
|
|
||||||
*format_ptr++ = '*';
|
|
||||||
}
|
|
||||||
if (precision >= 0) {
|
|
||||||
*format_ptr++ = '.';
|
|
||||||
*format_ptr++ = '*';
|
|
||||||
}
|
|
||||||
if (IsLongDouble<T>::VALUE)
|
|
||||||
*format_ptr++ = 'L';
|
|
||||||
*format_ptr++ = type;
|
|
||||||
*format_ptr = '\0';
|
|
||||||
|
|
||||||
// Format using snprintf.
|
|
||||||
for (;;) {
|
|
||||||
size_t size = buffer_.capacity() - offset;
|
|
||||||
int n = 0;
|
|
||||||
char *start = &buffer_[offset];
|
|
||||||
if (width_for_sprintf == 0) {
|
|
||||||
n = precision < 0 ?
|
|
||||||
snprintf(start, size, format, value) :
|
|
||||||
snprintf(start, size, format, precision, value);
|
|
||||||
} else {
|
|
||||||
n = precision < 0 ?
|
|
||||||
snprintf(start, size, format, width_for_sprintf, value) :
|
|
||||||
snprintf(start, size, format, width_for_sprintf, precision, value);
|
|
||||||
}
|
|
||||||
if (n >= 0 && offset + n < buffer_.capacity()) {
|
|
||||||
if (sign) {
|
|
||||||
if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) ||
|
|
||||||
*start != ' ') {
|
|
||||||
*(start - 1) = sign;
|
|
||||||
sign = 0;
|
|
||||||
} else {
|
|
||||||
*(start - 1) = spec.fill();
|
|
||||||
}
|
|
||||||
++n;
|
|
||||||
}
|
|
||||||
if (spec.align() == ALIGN_CENTER &&
|
|
||||||
spec.width() > static_cast<unsigned>(n)) {
|
|
||||||
char *p = GrowBuffer(spec.width());
|
|
||||||
std::copy(p, p + n, p + (spec.width() - n) / 2);
|
|
||||||
FillPadding(p, spec.width(), n, spec.fill());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (spec.fill() != ' ' || sign) {
|
|
||||||
while (*start == ' ')
|
|
||||||
*start++ = spec.fill();
|
|
||||||
if (sign)
|
|
||||||
*(start - 1) = sign;
|
|
||||||
}
|
|
||||||
GrowBuffer(n);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
buffer_.reserve(n >= 0 ? offset + n + 1 : 2 * buffer_.capacity());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *BasicFormatter::FormatString(
|
|
||||||
const char *s, std::size_t size, const FormatSpec &spec) {
|
|
||||||
char *out = 0;
|
|
||||||
if (spec.width() > size) {
|
|
||||||
out = GrowBuffer(spec.width());
|
|
||||||
if (spec.align() == ALIGN_RIGHT) {
|
|
||||||
std::fill_n(out, spec.width() - size, spec.fill());
|
|
||||||
out += spec.width() - size;
|
|
||||||
} else if (spec.align() == ALIGN_CENTER) {
|
|
||||||
out = FillPadding(out, spec.width(), size, spec.fill());
|
|
||||||
} else {
|
|
||||||
std::fill_n(out + size, spec.width() - size, spec.fill());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out = GrowBuffer(size);
|
|
||||||
}
|
|
||||||
std::copy(s, s + size, out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throws Exception(message) if format contains '}', otherwise throws
|
// Throws Exception(message) if format contains '}', otherwise throws
|
||||||
// FormatError reporting unmatched '{'. The idea is that unmatched '{'
|
// FormatError reporting unmatched '{'. The idea is that unmatched '{'
|
||||||
// should override other errors.
|
// should override other errors.
|
||||||
@ -573,7 +333,7 @@ void Formatter::DoFormat() {
|
|||||||
break;
|
break;
|
||||||
case CHAR: {
|
case CHAR: {
|
||||||
if (spec.type_ && spec.type_ != 'c')
|
if (spec.type_ && spec.type_ != 'c')
|
||||||
ReportUnknownType(spec.type_, "char");
|
internal::ReportUnknownType(spec.type_, "char");
|
||||||
char *out = 0;
|
char *out = 0;
|
||||||
if (spec.width_ > 1) {
|
if (spec.width_ > 1) {
|
||||||
out = GrowBuffer(spec.width_);
|
out = GrowBuffer(spec.width_);
|
||||||
@ -593,7 +353,7 @@ void Formatter::DoFormat() {
|
|||||||
}
|
}
|
||||||
case STRING: {
|
case STRING: {
|
||||||
if (spec.type_ && spec.type_ != 's')
|
if (spec.type_ && spec.type_ != 's')
|
||||||
ReportUnknownType(spec.type_, "string");
|
internal::ReportUnknownType(spec.type_, "string");
|
||||||
const char *str = arg.string.value;
|
const char *str = arg.string.value;
|
||||||
size_t size = arg.string.size;
|
size_t size = arg.string.size;
|
||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
@ -607,14 +367,14 @@ void Formatter::DoFormat() {
|
|||||||
}
|
}
|
||||||
case POINTER:
|
case POINTER:
|
||||||
if (spec.type_ && spec.type_ != 'p')
|
if (spec.type_ && spec.type_ != 'p')
|
||||||
ReportUnknownType(spec.type_, "pointer");
|
internal::ReportUnknownType(spec.type_, "pointer");
|
||||||
spec.flags_= HASH_FLAG;
|
spec.flags_= HASH_FLAG;
|
||||||
spec.type_ = 'x';
|
spec.type_ = 'x';
|
||||||
FormatInt(reinterpret_cast<uintptr_t>(arg.pointer_value), spec);
|
FormatInt(reinterpret_cast<uintptr_t>(arg.pointer_value), spec);
|
||||||
break;
|
break;
|
||||||
case CUSTOM:
|
case CUSTOM:
|
||||||
if (spec.type_)
|
if (spec.type_)
|
||||||
ReportUnknownType(spec.type_, "object");
|
internal::ReportUnknownType(spec.type_, "object");
|
||||||
(this->*arg.custom.format)(arg.custom.value, spec);
|
(this->*arg.custom.format)(arg.custom.value, spec);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
308
format.h
308
format.h
@ -136,6 +136,16 @@ struct IntTraits<int> : SignedIntTraits<int, unsigned> {};
|
|||||||
template <>
|
template <>
|
||||||
struct IntTraits<long> : SignedIntTraits<long, unsigned long> {};
|
struct IntTraits<long> : SignedIntTraits<long, unsigned long> {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct IsLongDouble { enum {VALUE = 0}; };
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct IsLongDouble<long double> { enum {VALUE = 1}; };
|
||||||
|
|
||||||
|
extern const char DIGITS[];
|
||||||
|
|
||||||
|
void ReportUnknownType(char code, const char *type);
|
||||||
|
|
||||||
class ArgInserter;
|
class ArgInserter;
|
||||||
class FormatterProxy;
|
class FormatterProxy;
|
||||||
}
|
}
|
||||||
@ -341,6 +351,7 @@ DEFINE_INT_FORMATTERS(long)
|
|||||||
DEFINE_INT_FORMATTERS(unsigned)
|
DEFINE_INT_FORMATTERS(unsigned)
|
||||||
DEFINE_INT_FORMATTERS(unsigned long)
|
DEFINE_INT_FORMATTERS(unsigned long)
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
class BasicFormatter {
|
class BasicFormatter {
|
||||||
private:
|
private:
|
||||||
// Returns the number of decimal digits in n. Trailing zeros are not counted
|
// Returns the number of decimal digits in n. Trailing zeros are not counted
|
||||||
@ -360,29 +371,30 @@ class BasicFormatter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void FormatDecimal(char *buffer, uint64_t value, unsigned num_digits);
|
static void FormatDecimal(Char *buffer, uint64_t value, unsigned num_digits);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void ReportUnknownType(char code, const char *type);
|
static Char *FillPadding(Char *buffer,
|
||||||
|
unsigned total_size, std::size_t content_size, char fill);
|
||||||
|
|
||||||
enum { INLINE_BUFFER_SIZE = 500 };
|
enum { INLINE_BUFFER_SIZE = 500 };
|
||||||
mutable internal::Array<char, INLINE_BUFFER_SIZE> buffer_; // Output buffer.
|
mutable internal::Array<Char, INLINE_BUFFER_SIZE> buffer_; // Output buffer.
|
||||||
|
|
||||||
// Grows the buffer by n characters and returns a pointer to the newly
|
// Grows the buffer by n characters and returns a pointer to the newly
|
||||||
// allocated area.
|
// allocated area.
|
||||||
char *GrowBuffer(std::size_t n) {
|
Char *GrowBuffer(std::size_t n) {
|
||||||
std::size_t size = buffer_.size();
|
std::size_t size = buffer_.size();
|
||||||
buffer_.resize(size + n);
|
buffer_.resize(size + n);
|
||||||
return &buffer_[size];
|
return &buffer_[size];
|
||||||
}
|
}
|
||||||
|
|
||||||
char *PrepareFilledBuffer(unsigned size, const Spec &, char sign) {
|
Char *PrepareFilledBuffer(unsigned size, const Spec &, char sign) {
|
||||||
char *p = GrowBuffer(size);
|
Char *p = GrowBuffer(size);
|
||||||
*p = sign;
|
*p = sign;
|
||||||
return p + size - 1;
|
return p + size - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *PrepareFilledBuffer(unsigned size, const AlignSpec &spec, char sign);
|
Char *PrepareFilledBuffer(unsigned size, const AlignSpec &spec, char sign);
|
||||||
|
|
||||||
// Formats an integer.
|
// Formats an integer.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -406,13 +418,13 @@ class BasicFormatter {
|
|||||||
Returns a pointer to the output buffer content. No terminating null
|
Returns a pointer to the output buffer content. No terminating null
|
||||||
character is appended.
|
character is appended.
|
||||||
*/
|
*/
|
||||||
const char *data() const { return &buffer_[0]; }
|
const Char *data() const { return &buffer_[0]; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns a pointer to the output buffer content with terminating null
|
Returns a pointer to the output buffer content with terminating null
|
||||||
character appended.
|
character appended.
|
||||||
*/
|
*/
|
||||||
const char *c_str() const {
|
const Char *c_str() const {
|
||||||
std::size_t size = buffer_.size();
|
std::size_t size = buffer_.size();
|
||||||
buffer_.reserve(size + 1);
|
buffer_.reserve(size + 1);
|
||||||
buffer_[size] = '\0';
|
buffer_[size] = '\0';
|
||||||
@ -422,7 +434,9 @@ class BasicFormatter {
|
|||||||
/**
|
/**
|
||||||
Returns the content of the output buffer as an `std::string`.
|
Returns the content of the output buffer as an `std::string`.
|
||||||
*/
|
*/
|
||||||
std::string str() const { return std::string(&buffer_[0], buffer_.size()); }
|
std::basic_string<Char> str() const {
|
||||||
|
return std::basic_string<Char>(&buffer_[0], buffer_.size());
|
||||||
|
}
|
||||||
|
|
||||||
BasicFormatter &operator<<(int value) {
|
BasicFormatter &operator<<(int value) {
|
||||||
return *this << IntFormatter<int, TypeSpec<0> >(value, TypeSpec<0>());
|
return *this << IntFormatter<int, TypeSpec<0> >(value, TypeSpec<0>());
|
||||||
@ -431,12 +445,12 @@ class BasicFormatter {
|
|||||||
return *this << IntFormatter<unsigned, TypeSpec<0> >(value, TypeSpec<0>());
|
return *this << IntFormatter<unsigned, TypeSpec<0> >(value, TypeSpec<0>());
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicFormatter &operator<<(char value) {
|
BasicFormatter &operator<<(Char value) {
|
||||||
*GrowBuffer(1) = value;
|
*GrowBuffer(1) = value;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicFormatter &operator<<(const char *value) {
|
BasicFormatter &operator<<(const Char *value) {
|
||||||
std::size_t size = std::strlen(value);
|
std::size_t size = std::strlen(value);
|
||||||
std::strncpy(GrowBuffer(size), value, size);
|
std::strncpy(GrowBuffer(size), value, size);
|
||||||
return *this;
|
return *this;
|
||||||
@ -445,7 +459,7 @@ class BasicFormatter {
|
|||||||
template <typename T, typename Spec>
|
template <typename T, typename Spec>
|
||||||
BasicFormatter &operator<<(const IntFormatter<T, Spec> &f);
|
BasicFormatter &operator<<(const IntFormatter<T, Spec> &f);
|
||||||
|
|
||||||
void Write(const std::string &s, const FormatSpec &spec) {
|
void Write(const std::basic_string<char> &s, const FormatSpec &spec) {
|
||||||
FormatString(s.data(), s.size(), spec);
|
FormatString(s.data(), s.size(), spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -454,8 +468,249 @@ class BasicFormatter {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fills the padding around the content and returns the pointer to the
|
||||||
|
// content area.
|
||||||
|
template <typename Char>
|
||||||
|
Char *BasicFormatter<Char>::FillPadding(Char *buffer,
|
||||||
|
unsigned total_size, std::size_t content_size, char fill) {
|
||||||
|
std::size_t padding = total_size - content_size;
|
||||||
|
std::size_t left_padding = padding / 2;
|
||||||
|
std::fill_n(buffer, left_padding, fill);
|
||||||
|
buffer += left_padding;
|
||||||
|
char *content = buffer;
|
||||||
|
std::fill_n(buffer + content_size, padding - left_padding, fill);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
void BasicFormatter<Char>::FormatDecimal(
|
||||||
|
Char *buffer, uint64_t value, unsigned num_digits) {
|
||||||
|
--num_digits;
|
||||||
|
while (value >= 100) {
|
||||||
|
// Integer division is slow so do it for a group of two digits instead
|
||||||
|
// of for every digit. The idea comes from the talk by Alexandrescu
|
||||||
|
// "Three Optimization Tips for C++". See speed-test for a comparison.
|
||||||
|
unsigned index = (value % 100) * 2;
|
||||||
|
value /= 100;
|
||||||
|
buffer[num_digits] = internal::DIGITS[index + 1];
|
||||||
|
buffer[num_digits - 1] = internal::DIGITS[index];
|
||||||
|
num_digits -= 2;
|
||||||
|
}
|
||||||
|
if (value < 10) {
|
||||||
|
*buffer = static_cast<char>('0' + value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsigned index = static_cast<unsigned>(value * 2);
|
||||||
|
buffer[1] = internal::DIGITS[index + 1];
|
||||||
|
buffer[0] = internal::DIGITS[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
Char *BasicFormatter<Char>::PrepareFilledBuffer(
|
||||||
|
unsigned size, const AlignSpec &spec, char sign) {
|
||||||
|
unsigned width = spec.width();
|
||||||
|
if (width <= size) {
|
||||||
|
char *p = GrowBuffer(size);
|
||||||
|
*p = sign;
|
||||||
|
return p + size - 1;
|
||||||
|
}
|
||||||
|
char *p = GrowBuffer(width);
|
||||||
|
char *end = p + width;
|
||||||
|
Alignment align = spec.align();
|
||||||
|
if (align == ALIGN_LEFT) {
|
||||||
|
*p = sign;
|
||||||
|
p += size;
|
||||||
|
std::fill(p, end, spec.fill());
|
||||||
|
} else if (align == ALIGN_CENTER) {
|
||||||
|
p = FillPadding(p, width, size, spec.fill());
|
||||||
|
*p = sign;
|
||||||
|
p += size;
|
||||||
|
} else {
|
||||||
|
if (align == ALIGN_NUMERIC) {
|
||||||
|
if (sign) {
|
||||||
|
*p++ = sign;
|
||||||
|
--size;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*(end - size) = sign;
|
||||||
|
}
|
||||||
|
std::fill(p, end - size, spec.fill());
|
||||||
|
p = end;
|
||||||
|
}
|
||||||
|
return p - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
template <typename T>
|
||||||
|
void BasicFormatter<Char>::FormatDouble(
|
||||||
|
T value, const FormatSpec &spec, int precision) {
|
||||||
|
// Check type.
|
||||||
|
char type = spec.type();
|
||||||
|
bool upper = false;
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
type = 'g';
|
||||||
|
break;
|
||||||
|
case 'e': case 'f': case 'g':
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
// MSVC's printf doesn't support 'F'.
|
||||||
|
type = 'f';
|
||||||
|
#endif
|
||||||
|
// Fall through.
|
||||||
|
case 'E': case 'G':
|
||||||
|
upper = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
internal::ReportUnknownType(type, "double");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char sign = 0;
|
||||||
|
// Use SignBit instead of value < 0 because the latter is always
|
||||||
|
// false for NaN.
|
||||||
|
if (SignBit(value)) {
|
||||||
|
sign = '-';
|
||||||
|
value = -value;
|
||||||
|
} else if (spec.sign_flag()) {
|
||||||
|
sign = spec.plus_flag() ? '+' : ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != value) {
|
||||||
|
// Format NaN ourselves because sprintf's output is not consistent
|
||||||
|
// across platforms.
|
||||||
|
std::size_t size = 4;
|
||||||
|
const char *nan = upper ? " NAN" : " nan";
|
||||||
|
if (!sign) {
|
||||||
|
--size;
|
||||||
|
++nan;
|
||||||
|
}
|
||||||
|
char *out = FormatString(nan, size, spec);
|
||||||
|
if (sign)
|
||||||
|
*out = sign;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isinf(value)) {
|
||||||
|
// Format infinity ourselves because sprintf's output is not consistent
|
||||||
|
// across platforms.
|
||||||
|
std::size_t size = 4;
|
||||||
|
const char *inf = upper ? " INF" : " inf";
|
||||||
|
if (!sign) {
|
||||||
|
--size;
|
||||||
|
++inf;
|
||||||
|
}
|
||||||
|
char *out = FormatString(inf, size, spec);
|
||||||
|
if (sign)
|
||||||
|
*out = sign;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t offset = buffer_.size();
|
||||||
|
unsigned width = spec.width();
|
||||||
|
if (sign) {
|
||||||
|
buffer_.reserve(buffer_.size() + std::max(width, 1u));
|
||||||
|
if (width > 0)
|
||||||
|
--width;
|
||||||
|
++offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build format string.
|
||||||
|
enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg
|
||||||
|
char format[MAX_FORMAT_SIZE];
|
||||||
|
char *format_ptr = format;
|
||||||
|
*format_ptr++ = '%';
|
||||||
|
unsigned width_for_sprintf = width;
|
||||||
|
if (spec.hash_flag())
|
||||||
|
*format_ptr++ = '#';
|
||||||
|
if (spec.align() == ALIGN_CENTER) {
|
||||||
|
width_for_sprintf = 0;
|
||||||
|
} else {
|
||||||
|
if (spec.align() == ALIGN_LEFT)
|
||||||
|
*format_ptr++ = '-';
|
||||||
|
if (width != 0)
|
||||||
|
*format_ptr++ = '*';
|
||||||
|
}
|
||||||
|
if (precision >= 0) {
|
||||||
|
*format_ptr++ = '.';
|
||||||
|
*format_ptr++ = '*';
|
||||||
|
}
|
||||||
|
if (internal::IsLongDouble<T>::VALUE)
|
||||||
|
*format_ptr++ = 'L';
|
||||||
|
*format_ptr++ = type;
|
||||||
|
*format_ptr = '\0';
|
||||||
|
|
||||||
|
// Format using snprintf.
|
||||||
|
for (;;) {
|
||||||
|
size_t size = buffer_.capacity() - offset;
|
||||||
|
int n = 0;
|
||||||
|
char *start = &buffer_[offset];
|
||||||
|
if (width_for_sprintf == 0) {
|
||||||
|
n = precision < 0 ?
|
||||||
|
snprintf(start, size, format, value) :
|
||||||
|
snprintf(start, size, format, precision, value);
|
||||||
|
} else {
|
||||||
|
n = precision < 0 ?
|
||||||
|
snprintf(start, size, format, width_for_sprintf, value) :
|
||||||
|
snprintf(start, size, format, width_for_sprintf, precision, value);
|
||||||
|
}
|
||||||
|
if (n >= 0 && offset + n < buffer_.capacity()) {
|
||||||
|
if (sign) {
|
||||||
|
if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) ||
|
||||||
|
*start != ' ') {
|
||||||
|
*(start - 1) = sign;
|
||||||
|
sign = 0;
|
||||||
|
} else {
|
||||||
|
*(start - 1) = spec.fill();
|
||||||
|
}
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
if (spec.align() == ALIGN_CENTER &&
|
||||||
|
spec.width() > static_cast<unsigned>(n)) {
|
||||||
|
char *p = GrowBuffer(spec.width());
|
||||||
|
std::copy(p, p + n, p + (spec.width() - n) / 2);
|
||||||
|
FillPadding(p, spec.width(), n, spec.fill());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (spec.fill() != ' ' || sign) {
|
||||||
|
while (*start == ' ')
|
||||||
|
*start++ = spec.fill();
|
||||||
|
if (sign)
|
||||||
|
*(start - 1) = sign;
|
||||||
|
}
|
||||||
|
GrowBuffer(n);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buffer_.reserve(n >= 0 ? offset + n + 1 : 2 * buffer_.capacity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
char *BasicFormatter<Char>::FormatString(
|
||||||
|
const char *s, std::size_t size, const FormatSpec &spec) {
|
||||||
|
char *out = 0;
|
||||||
|
if (spec.width() > size) {
|
||||||
|
out = GrowBuffer(spec.width());
|
||||||
|
if (spec.align() == ALIGN_RIGHT) {
|
||||||
|
std::fill_n(out, spec.width() - size, spec.fill());
|
||||||
|
out += spec.width() - size;
|
||||||
|
} else if (spec.align() == ALIGN_CENTER) {
|
||||||
|
out = FillPadding(out, spec.width(), size, spec.fill());
|
||||||
|
} else {
|
||||||
|
std::fill_n(out + size, spec.width() - size, spec.fill());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out = GrowBuffer(size);
|
||||||
|
}
|
||||||
|
std::copy(s, s + size, out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
template <typename T, typename Spec>
|
template <typename T, typename Spec>
|
||||||
BasicFormatter &BasicFormatter::operator<<(const IntFormatter<T, Spec> &f) {
|
BasicFormatter<Char> &BasicFormatter<Char>::operator<<(
|
||||||
|
const IntFormatter<T, Spec> &f) {
|
||||||
T value = f.value();
|
T value = f.value();
|
||||||
unsigned size = 0;
|
unsigned size = 0;
|
||||||
char sign = 0;
|
char sign = 0;
|
||||||
@ -472,7 +727,7 @@ BasicFormatter &BasicFormatter::operator<<(const IntFormatter<T, Spec> &f) {
|
|||||||
switch (f.type()) {
|
switch (f.type()) {
|
||||||
case 0: case 'd': {
|
case 0: case 'd': {
|
||||||
unsigned num_digits = BasicFormatter::CountDigits(abs_value);
|
unsigned num_digits = BasicFormatter::CountDigits(abs_value);
|
||||||
char *p = PrepareFilledBuffer(size + num_digits, f, sign) - num_digits + 1;
|
Char *p = PrepareFilledBuffer(size + num_digits, f, sign) - num_digits + 1;
|
||||||
BasicFormatter::FormatDecimal(p, abs_value, num_digits);
|
BasicFormatter::FormatDecimal(p, abs_value, num_digits);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -483,7 +738,7 @@ BasicFormatter &BasicFormatter::operator<<(const IntFormatter<T, Spec> &f) {
|
|||||||
do {
|
do {
|
||||||
++size;
|
++size;
|
||||||
} while ((n >>= 4) != 0);
|
} while ((n >>= 4) != 0);
|
||||||
char *p = PrepareFilledBuffer(size, f, sign);
|
Char *p = PrepareFilledBuffer(size, f, sign);
|
||||||
n = abs_value;
|
n = abs_value;
|
||||||
const char *digits = f.type() == 'x' ?
|
const char *digits = f.type() == 'x' ?
|
||||||
"0123456789abcdef" : "0123456789ABCDEF";
|
"0123456789abcdef" : "0123456789ABCDEF";
|
||||||
@ -503,7 +758,7 @@ BasicFormatter &BasicFormatter::operator<<(const IntFormatter<T, Spec> &f) {
|
|||||||
do {
|
do {
|
||||||
++size;
|
++size;
|
||||||
} while ((n >>= 3) != 0);
|
} while ((n >>= 3) != 0);
|
||||||
char *p = PrepareFilledBuffer(size, f, sign);
|
Char *p = PrepareFilledBuffer(size, f, sign);
|
||||||
n = abs_value;
|
n = abs_value;
|
||||||
do {
|
do {
|
||||||
*p-- = '0' + (n & 7);
|
*p-- = '0' + (n & 7);
|
||||||
@ -513,16 +768,16 @@ BasicFormatter &BasicFormatter::operator<<(const IntFormatter<T, Spec> &f) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
BasicFormatter::ReportUnknownType(f.type(), "integer");
|
internal::ReportUnknownType(f.type(), "integer");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The default formatting function.
|
// The default formatting function.
|
||||||
template <typename T>
|
template <typename Char, typename T>
|
||||||
void Format(BasicFormatter &f, const FormatSpec &spec, const T &value) {
|
void Format(BasicFormatter<Char> &f, const FormatSpec &spec, const T &value) {
|
||||||
std::ostringstream os;
|
std::basic_ostringstream<Char> os;
|
||||||
os << value;
|
os << value;
|
||||||
f.Write(os.str(), spec);
|
f.Write(os.str(), spec);
|
||||||
}
|
}
|
||||||
@ -551,7 +806,7 @@ void Format(BasicFormatter &f, const FormatSpec &spec, const T &value) {
|
|||||||
The buffer can be accessed using :meth:`data` or :meth:`c_str`.
|
The buffer can be accessed using :meth:`data` or :meth:`c_str`.
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
class Formatter : public BasicFormatter {
|
class Formatter : public BasicFormatter<char> {
|
||||||
private:
|
private:
|
||||||
enum Type {
|
enum Type {
|
||||||
// Numeric types should go first.
|
// Numeric types should go first.
|
||||||
@ -704,8 +959,13 @@ class Formatter : public BasicFormatter {
|
|||||||
internal::ArgInserter operator()(StringRef format);
|
internal::ArgInserter operator()(StringRef format);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::string str(const BasicFormatter &f) { return f.str(); }
|
template <typename Char>
|
||||||
inline const char *c_str(const BasicFormatter &f) { return f.c_str(); }
|
inline std::basic_string<Char> str(const BasicFormatter<Char> &f) {
|
||||||
|
return f.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
inline const Char *c_str(const BasicFormatter<Char> &f) { return f.c_str(); }
|
||||||
|
|
||||||
std::string str(internal::FormatterProxy p);
|
std::string str(internal::FormatterProxy p);
|
||||||
const char *c_str(internal::FormatterProxy p);
|
const char *c_str(internal::FormatterProxy p);
|
||||||
|
@ -862,7 +862,9 @@ class Date {
|
|||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
friend BasicFormatter &operator<<(BasicFormatter &f, const Date &d) {
|
template <typename Char>
|
||||||
|
friend BasicFormatter<Char> &operator<<(
|
||||||
|
BasicFormatter<Char> &f, const Date &d) {
|
||||||
return f << d.year_ << '-' << d.month_ << '-' << d.day_;
|
return f << d.year_ << '-' << d.month_ << '-' << d.day_;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -877,7 +879,8 @@ TEST(FormatterTest, FormatUsingIOStreams) {
|
|||||||
|
|
||||||
class Answer {};
|
class Answer {};
|
||||||
|
|
||||||
void Format(fmt::BasicFormatter &f, const fmt::FormatSpec &spec, Answer) {
|
template <typename Char>
|
||||||
|
void Format(fmt::BasicFormatter<Char> &f, const fmt::FormatSpec &spec, Answer) {
|
||||||
f.Write("42", spec);
|
f.Write("42", spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -918,7 +921,7 @@ TEST(FormatterTest, FormatterAppend) {
|
|||||||
|
|
||||||
TEST(FormatterTest, FormatterExamples) {
|
TEST(FormatterTest, FormatterExamples) {
|
||||||
using fmt::hex;
|
using fmt::hex;
|
||||||
EXPECT_EQ("0000cafe", str(BasicFormatter() << pad(hex(0xcafe), 8, '0')));
|
EXPECT_EQ("0000cafe", str(BasicFormatter<char>() << pad(hex(0xcafe), 8, '0')));
|
||||||
|
|
||||||
std::string message = str(Format("The answer is {}") << 42);
|
std::string message = str(Format("The answer is {}") << 42);
|
||||||
EXPECT_EQ("The answer is 42", message);
|
EXPECT_EQ("The answer is 42", message);
|
||||||
@ -1068,11 +1071,11 @@ TEST(TempFormatterTest, Examples) {
|
|||||||
|
|
||||||
TEST(StrTest, oct) {
|
TEST(StrTest, oct) {
|
||||||
using fmt::oct;
|
using fmt::oct;
|
||||||
EXPECT_EQ("12", (BasicFormatter() << oct(static_cast<short>(012))).str());
|
EXPECT_EQ("12", str(BasicFormatter<char>() << oct(static_cast<short>(012))));
|
||||||
EXPECT_EQ("12", (BasicFormatter() << oct(012)).str());
|
EXPECT_EQ("12", str(BasicFormatter<char>() << oct(012)));
|
||||||
EXPECT_EQ("34", (BasicFormatter() << oct(034u)).str());
|
EXPECT_EQ("34", str(BasicFormatter<char>() << oct(034u)));
|
||||||
EXPECT_EQ("56", (BasicFormatter() << oct(056l)).str());
|
EXPECT_EQ("56", str(BasicFormatter<char>() << oct(056l)));
|
||||||
EXPECT_EQ("70", (BasicFormatter() << oct(070ul)).str());
|
EXPECT_EQ("70", str(BasicFormatter<char>() << oct(070ul)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(StrTest, hex) {
|
TEST(StrTest, hex) {
|
||||||
@ -1082,17 +1085,17 @@ TEST(StrTest, hex) {
|
|||||||
// This shouldn't compile:
|
// This shouldn't compile:
|
||||||
//fmt::IntFormatter<short, fmt::TypeSpec<'x'> > (*phex2)(short value) = hex;
|
//fmt::IntFormatter<short, fmt::TypeSpec<'x'> > (*phex2)(short value) = hex;
|
||||||
|
|
||||||
EXPECT_EQ("cafe", (BasicFormatter() << hex(0xcafe)).str());
|
EXPECT_EQ("cafe", str(BasicFormatter<char>() << hex(0xcafe)));
|
||||||
EXPECT_EQ("babe", (BasicFormatter() << hex(0xbabeu)).str());
|
EXPECT_EQ("babe", str(BasicFormatter<char>() << hex(0xbabeu)));
|
||||||
EXPECT_EQ("dead", (BasicFormatter() << hex(0xdeadl)).str());
|
EXPECT_EQ("dead", str(BasicFormatter<char>() << hex(0xdeadl)));
|
||||||
EXPECT_EQ("beef", (BasicFormatter() << hex(0xbeeful)).str());
|
EXPECT_EQ("beef", str(BasicFormatter<char>() << hex(0xbeeful)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(StrTest, hexu) {
|
TEST(StrTest, hexu) {
|
||||||
EXPECT_EQ("CAFE", (BasicFormatter() << hexu(0xcafe)).str());
|
EXPECT_EQ("CAFE", str(BasicFormatter<char>() << hexu(0xcafe)));
|
||||||
EXPECT_EQ("BABE", (BasicFormatter() << hexu(0xbabeu)).str());
|
EXPECT_EQ("BABE", str(BasicFormatter<char>() << hexu(0xbabeu)));
|
||||||
EXPECT_EQ("DEAD", (BasicFormatter() << hexu(0xdeadl)).str());
|
EXPECT_EQ("DEAD", str(BasicFormatter<char>() << hexu(0xdeadl)));
|
||||||
EXPECT_EQ("BEEF", (BasicFormatter() << hexu(0xbeeful)).str());
|
EXPECT_EQ("BEEF", str(BasicFormatter<char>() << hexu(0xbeeful)));
|
||||||
}
|
}
|
||||||
|
|
||||||
class ISO8601DateFormatter {
|
class ISO8601DateFormatter {
|
||||||
@ -1101,8 +1104,9 @@ class ISO8601DateFormatter {
|
|||||||
public:
|
public:
|
||||||
ISO8601DateFormatter(const Date &d) : date_(&d) {}
|
ISO8601DateFormatter(const Date &d) : date_(&d) {}
|
||||||
|
|
||||||
friend BasicFormatter &operator<<(
|
template <typename Char>
|
||||||
BasicFormatter &f, const ISO8601DateFormatter &d) {
|
friend BasicFormatter<Char> &operator<<(
|
||||||
|
BasicFormatter<Char> &f, const ISO8601DateFormatter &d) {
|
||||||
return f << pad(d.date_->year(), 4, '0') << '-'
|
return f << pad(d.date_->year(), 4, '0') << '-'
|
||||||
<< pad(d.date_->month(), 2, '0') << '-' << pad(d.date_->day(), 2, '0');
|
<< pad(d.date_->month(), 2, '0') << '-' << pad(d.date_->day(), 2, '0');
|
||||||
}
|
}
|
||||||
@ -1112,17 +1116,17 @@ ISO8601DateFormatter iso8601(const Date &d) { return ISO8601DateFormatter(d); }
|
|||||||
|
|
||||||
TEST(StrTest, pad) {
|
TEST(StrTest, pad) {
|
||||||
using fmt::hex;
|
using fmt::hex;
|
||||||
EXPECT_EQ(" cafe", (BasicFormatter() << pad(hex(0xcafe), 8)).str());
|
EXPECT_EQ(" cafe", str(BasicFormatter<char>() << pad(hex(0xcafe), 8)));
|
||||||
EXPECT_EQ(" babe", (BasicFormatter() << pad(hex(0xbabeu), 8)).str());
|
EXPECT_EQ(" babe", str(BasicFormatter<char>() << pad(hex(0xbabeu), 8)));
|
||||||
EXPECT_EQ(" dead", (BasicFormatter() << pad(hex(0xdeadl), 8)).str());
|
EXPECT_EQ(" dead", str(BasicFormatter<char>() << pad(hex(0xdeadl), 8)));
|
||||||
EXPECT_EQ(" beef", (BasicFormatter() << pad(hex(0xbeeful), 8)).str());
|
EXPECT_EQ(" beef", str(BasicFormatter<char>() << pad(hex(0xbeeful), 8)));
|
||||||
|
|
||||||
EXPECT_EQ(" 11", (BasicFormatter() << pad(11, 7)).str());
|
EXPECT_EQ(" 11", str(BasicFormatter<char>() << pad(11, 7)));
|
||||||
EXPECT_EQ(" 22", (BasicFormatter() << pad(22u, 7)).str());
|
EXPECT_EQ(" 22", str(BasicFormatter<char>() << pad(22u, 7)));
|
||||||
EXPECT_EQ(" 33", (BasicFormatter() << pad(33l, 7)).str());
|
EXPECT_EQ(" 33", str(BasicFormatter<char>() << pad(33l, 7)));
|
||||||
EXPECT_EQ(" 44", (BasicFormatter() << pad(44lu, 7)).str());
|
EXPECT_EQ(" 44", str(BasicFormatter<char>() << pad(44lu, 7)));
|
||||||
|
|
||||||
BasicFormatter f;
|
BasicFormatter<char> f;
|
||||||
f.Clear();
|
f.Clear();
|
||||||
f << pad(42, 5, '0');
|
f << pad(42, 5, '0');
|
||||||
EXPECT_EQ("00042", f.str());
|
EXPECT_EQ("00042", f.str());
|
||||||
@ -1137,8 +1141,12 @@ TEST(StrTest, pad) {
|
|||||||
TEST(StrTest, NoConflictWithIOManip) {
|
TEST(StrTest, NoConflictWithIOManip) {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace fmt;
|
using namespace fmt;
|
||||||
EXPECT_EQ("cafe", (BasicFormatter() << hex(0xcafe)).str());
|
EXPECT_EQ("cafe", str(BasicFormatter<char>() << hex(0xcafe)));
|
||||||
EXPECT_EQ("12", (BasicFormatter() << oct(012)).str());
|
EXPECT_EQ("12", str(BasicFormatter<char>() << oct(012)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StrTest, BasicFormatterWChar) {
|
||||||
|
EXPECT_EQ(L"cafe", str(BasicFormatter<wchar_t>() << fmt::hex(0xcafe)));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
Loading…
Reference in New Issue
Block a user