/*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2015 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#error This file may only be included from Format.h.
#endif
-#include "folly/Exception.h"
-#include "folly/Traits.h"
+#include <array>
+#include <cinttypes>
+#include <deque>
+#include <map>
+#include <unordered_map>
+#include <vector>
+
+#include <folly/Exception.h>
+#include <folly/FormatTraits.h>
+#include <folly/Traits.h>
+
+// Ignore -Wformat-nonliteral warnings within this file
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
namespace folly {
namespace detail {
+// Updates the end of the buffer after the comma separators have been added.
+void insertThousandsGroupingUnsafe(char* start_buffer, char** end_buffer);
+
extern const char formatHexUpper[256][2];
extern const char formatHexLower[256][2];
extern const char formatOctal[512][3];
} // namespace detail
-
-template <bool containerMode, class... Args>
-Formatter<containerMode, Args...>::Formatter(StringPiece str, Args&&... args)
- : str_(str),
- values_(FormatValue<typename std::decay<Args>::type>(
- std::forward<Args>(args))...) {
+template <class Derived, bool containerMode, class... Args>
+BaseFormatter<Derived, containerMode, Args...>::BaseFormatter(StringPiece str,
+ Args&&... args)
+ : str_(str),
+ values_(FormatValue<typename std::decay<Args>::type>(
+ std::forward<Args>(args))...) {
static_assert(!containerMode || sizeof...(Args) == 1,
"Exactly one argument required in container mode");
}
-template <bool containerMode, class... Args>
+template <class Derived, bool containerMode, class... Args>
template <class Output>
-void Formatter<containerMode, Args...>::operator()(Output& out) const {
- auto p = str_.begin();
- auto end = str_.end();
-
+void BaseFormatter<Derived, containerMode, Args...>::operator()(Output& out)
+ const {
// Copy raw string (without format specifiers) to output;
// not as simple as we'd like, as we still need to translate "}}" to "}"
// and throw if we see any lone "}"
out(StringPiece(p, q));
p = q;
- CHECK(p != end && *p == '}') << "single '}' in format string";
+ if (p == end || *p != '}') {
+ throw BadFormatArg("folly::format: single '}' in format string");
+ }
++p;
}
};
+ auto p = str_.begin();
+ auto end = str_.end();
+
int nextArg = 0;
bool hasDefaultArgIndex = false;
bool hasExplicitArgIndex = false;
outputString(StringPiece(p, q));
p = q + 1;
- CHECK(p != end) << "'{' at end of format string";
+ if (p == end) {
+ throw BadFormatArg("folly::format: '}' at end of format string");
+ }
// "{{" -> "{"
if (*p == '{') {
// Format string
q = static_cast<const char*>(memchr(p, '}', end - p));
- CHECK(q != end) << "missing ending '}'";
+ if (q == nullptr) {
+ throw BadFormatArg("folly::format: missing ending '}'");
+ }
FormatArg arg(StringPiece(p, q));
p = q + 1;
int argIndex = 0;
auto piece = arg.splitKey<true>(); // empty key component is okay
if (containerMode) { // static
+ arg.enforce(arg.width != FormatArg::kDynamicWidth,
+ "dynamic field width not supported in vformat()");
if (piece.empty()) {
arg.setNextIntKey(nextArg++);
hasDefaultArgIndex = true;
}
} else {
if (piece.empty()) {
+ if (arg.width == FormatArg::kDynamicWidth) {
+ arg.enforce(arg.widthIndex == FormatArg::kNoIndex,
+ "cannot provide width arg index without value arg index");
+ int sizeArg = nextArg++;
+ arg.width = getSizeArg(sizeArg, arg);
+ }
+
argIndex = nextArg++;
hasDefaultArgIndex = true;
} else {
+ if (arg.width == FormatArg::kDynamicWidth) {
+ arg.enforce(arg.widthIndex != FormatArg::kNoIndex,
+ "cannot provide value arg index without width arg index");
+ arg.width = getSizeArg(arg.widthIndex, arg);
+ }
+
try {
argIndex = to<int>(piece);
} catch (const std::out_of_range& e) {
- LOG(FATAL) << "argument index must be integer";
+ arg.error("argument index must be integer");
}
- CHECK(argIndex >= 0)
- << arg.errorStr("argument index must be non-negative");
+ arg.enforce(argIndex >= 0, "argument index must be non-negative");
hasExplicitArgIndex = true;
}
}
- CHECK(!hasDefaultArgIndex || !hasExplicitArgIndex)
- << "may not have both default and explicit arg indexes";
+ if (hasDefaultArgIndex && hasExplicitArgIndex) {
+ throw BadFormatArg(
+ "folly::format: may not have both default and explicit arg indexes");
+ }
doFormat(argIndex, arg, out);
}
}
-template <bool containerMode, class... Args>
-void writeTo(FILE* fp, const Formatter<containerMode, Args...>& formatter) {
+template <class Derived, bool containerMode, class... Args>
+void writeTo(FILE* fp,
+ const BaseFormatter<Derived, containerMode, Args...>& formatter) {
auto writer = [fp] (StringPiece sp) {
- ssize_t n = fwrite(sp.data(), 1, sp.size(), fp);
+ size_t n = fwrite(sp.data(), 1, sp.size(), fp);
if (n < sp.size()) {
throwSystemError("Formatter writeTo", "fwrite failed");
}
template <class FormatCallback>
void formatString(StringPiece val, FormatArg& arg, FormatCallback& cb) {
+ if (arg.width != FormatArg::kDefaultWidth && arg.width < 0) {
+ throw BadFormatArg("folly::format: invalid width");
+ }
+ if (arg.precision != FormatArg::kDefaultPrecision && arg.precision < 0) {
+ throw BadFormatArg("folly::format: invalid precision");
+ }
+
+ // XXX: clang should be smart enough to not need the two static_cast<size_t>
+ // uses below given the above checks. If clang ever becomes that smart, we
+ // should remove the otherwise unnecessary warts.
+
if (arg.precision != FormatArg::kDefaultPrecision &&
- val.size() > arg.precision) {
+ val.size() > static_cast<size_t>(arg.precision)) {
val.reset(val.data(), arg.precision);
}
};
int padRemaining = 0;
- if (arg.width != FormatArg::kDefaultWidth && val.size() < arg.width) {
+ if (arg.width != FormatArg::kDefaultWidth &&
+ val.size() < static_cast<size_t>(arg.width)) {
char fill = arg.fill == FormatArg::kDefaultFill ? ' ' : arg.fill;
- int padChars = arg.width - val.size();
+ int padChars = static_cast<int> (arg.width - val.size());
memset(padBuf, fill, std::min(padBufSize, padChars));
switch (arg.align) {
format_value::formatString(val, arg, cb);
}
-template <class FormatCallback, bool containerMode, class... Args>
-void formatFormatter(const Formatter<containerMode, Args...>& formatter,
- FormatArg& arg,
- FormatCallback& cb) {
+template <class FormatCallback,
+ class Derived,
+ bool containerMode,
+ class... Args>
+void formatFormatter(
+ const BaseFormatter<Derived, containerMode, Args...>& formatter,
+ FormatArg& arg,
+ FormatCallback& cb) {
if (arg.width == FormatArg::kDefaultWidth &&
arg.precision == FormatArg::kDefaultPrecision) {
// nothing to do
{
public:
explicit FormatValue(T val) : val_(val) { }
+
+ T getValue() const {
+ return val_;
+ }
+
template <class FormatCallback>
void format(FormatArg& arg, FormatCallback& cb) const {
arg.validate(FormatArg::Type::INTEGER);
uval = val_;
sign = '\0';
- CHECK(arg.sign == FormatArg::Sign::DEFAULT)
- << arg.errorStr("sign specifications not allowed for unsigned values");
+ arg.enforce(arg.sign == FormatArg::Sign::DEFAULT,
+ "sign specifications not allowed for unsigned values");
}
// max of:
char* valBufBegin = nullptr;
char* valBufEnd = nullptr;
- // Defer to sprintf
- auto useSprintf = [&] (const char* format) mutable {
- valBufBegin = valBuf + 3; // room for sign and base prefix
- valBufEnd = valBufBegin + sprintf(valBufBegin, format,
- static_cast<uintmax_t>(uval));
- };
-
int prefixLen = 0;
-
switch (presentation) {
- case 'n': // TODO(tudorb): locale awareness?
+ case 'n': {
+ arg.enforce(!arg.basePrefix,
+ "base prefix not allowed with '", presentation,
+ "' specifier");
+
+ arg.enforce(!arg.thousandsSeparator,
+ "cannot use ',' with the '", presentation,
+ "' specifier");
+
+ valBufBegin = valBuf + 3; // room for sign and base prefix
+#ifdef _MSC_VER
+ char valBuf2[valBufSize];
+ snprintf(valBuf2, valBufSize, "%ju", static_cast<uintmax_t>(uval));
+ int len = GetNumberFormat(
+ LOCALE_USER_DEFAULT,
+ 0,
+ valBuf2,
+ nullptr,
+ valBufBegin,
+ (int)((valBuf + valBufSize) - valBufBegin)
+ );
+#elif defined(__ANDROID__)
+ int len = snprintf(valBufBegin, (valBuf + valBufSize) - valBufBegin,
+ "%" PRIuMAX, static_cast<uintmax_t>(uval));
+#else
+ int len = snprintf(valBufBegin, (valBuf + valBufSize) - valBufBegin,
+ "%'ju", static_cast<uintmax_t>(uval));
+#endif
+ // valBufSize should always be big enough, so this should never
+ // happen.
+ assert(len < valBuf + valBufSize - valBufBegin);
+ valBufEnd = valBufBegin + len;
+ break;
+ }
case 'd':
- CHECK(!arg.basePrefix)
- << arg.errorStr("base prefix not allowed with '", presentation,
- "' specifier");
+ arg.enforce(!arg.basePrefix,
+ "base prefix not allowed with '", presentation,
+ "' specifier");
+ valBufBegin = valBuf + 3; // room for sign and base prefix
+
+ // Use uintToBuffer, faster than sprintf
+ valBufEnd = valBufBegin + uint64ToBufferUnsafe(uval, valBufBegin);
if (arg.thousandsSeparator) {
- useSprintf("%'ju");
- } else {
- // Use uintToBuffer, faster than sprintf
- valBufBegin = valBuf + 3;
- valBufEnd = valBufBegin + uint64ToBufferUnsafe(uval, valBufBegin);
+ detail::insertThousandsGroupingUnsafe(valBufBegin, &valBufEnd);
}
break;
case 'c':
- CHECK(!arg.basePrefix)
- << arg.errorStr("base prefix not allowed with '", presentation,
- "' specifier");
- CHECK(!arg.thousandsSeparator)
- << arg.errorStr("thousands separator (',') not allowed with '",
- presentation, "' specifier");
+ arg.enforce(!arg.basePrefix,
+ "base prefix not allowed with '", presentation,
+ "' specifier");
+ arg.enforce(!arg.thousandsSeparator,
+ "thousands separator (',') not allowed with '",
+ presentation, "' specifier");
valBufBegin = valBuf + 3;
*valBufBegin = static_cast<char>(uval);
valBufEnd = valBufBegin + 1;
break;
case 'o':
case 'O':
- CHECK(!arg.thousandsSeparator)
- << arg.errorStr("thousands separator (',') not allowed with '",
- presentation, "' specifier");
+ arg.enforce(!arg.thousandsSeparator,
+ "thousands separator (',') not allowed with '",
+ presentation, "' specifier");
valBufEnd = valBuf + valBufSize - 1;
valBufBegin = valBuf + detail::uintToOctal(valBuf, valBufSize - 1, uval);
if (arg.basePrefix) {
}
break;
case 'x':
- CHECK(!arg.thousandsSeparator)
- << arg.errorStr("thousands separator (',') not allowed with '",
- presentation, "' specifier");
+ arg.enforce(!arg.thousandsSeparator,
+ "thousands separator (',') not allowed with '",
+ presentation, "' specifier");
valBufEnd = valBuf + valBufSize - 1;
valBufBegin = valBuf + detail::uintToHexLower(valBuf, valBufSize - 1,
uval);
}
break;
case 'X':
- CHECK(!arg.thousandsSeparator)
- << arg.errorStr("thousands separator (',') not allowed with '",
- presentation, "' specifier");
+ arg.enforce(!arg.thousandsSeparator,
+ "thousands separator (',') not allowed with '",
+ presentation, "' specifier");
valBufEnd = valBuf + valBufSize - 1;
valBufBegin = valBuf + detail::uintToHexUpper(valBuf, valBufSize - 1,
uval);
break;
case 'b':
case 'B':
- CHECK(!arg.thousandsSeparator)
- << arg.errorStr("thousands separator (',') not allowed with '",
- presentation, "' specifier");
+ arg.enforce(!arg.thousandsSeparator,
+ "thousands separator (',') not allowed with '",
+ presentation, "' specifier");
valBufEnd = valBuf + valBufSize - 1;
valBufBegin = valBuf + detail::uintToBinary(valBuf, valBufSize - 1,
uval);
}
break;
default:
- LOG(FATAL) << arg.errorStr("invalid specifier '", presentation, "'");
+ arg.error("invalid specifier '", presentation, "'");
}
if (sign) {
template <class FormatCallback>
void format(FormatArg& arg, FormatCallback& cb) const {
- using ::double_conversion::DoubleToStringConverter;
- using ::double_conversion::StringBuilder;
-
- arg.validate(FormatArg::Type::FLOAT);
-
- if (arg.presentation == FormatArg::kDefaultPresentation) {
- arg.presentation = 'g';
- }
-
- const char* infinitySymbol = isupper(arg.presentation) ? "INF" : "inf";
- const char* nanSymbol = isupper(arg.presentation) ? "NAN" : "nan";
- char exponentSymbol = isupper(arg.presentation) ? 'E' : 'e';
-
- if (arg.precision == FormatArg::kDefaultPrecision) {
- arg.precision = 6;
- }
-
- // 2+: for null terminator and optional sign shenanigans.
- char buf[2 + std::max({
- (2 + DoubleToStringConverter::kMaxFixedDigitsBeforePoint +
- DoubleToStringConverter::kMaxFixedDigitsAfterPoint),
- (8 + DoubleToStringConverter::kMaxExponentialDigits),
- (7 + DoubleToStringConverter::kMaxPrecisionDigits)})];
- StringBuilder builder(buf + 1, sizeof(buf) - 1);
-
- char plusSign;
- switch (arg.sign) {
- case FormatArg::Sign::PLUS_OR_MINUS:
- plusSign = '+';
- break;
- case FormatArg::Sign::SPACE_OR_MINUS:
- plusSign = ' ';
- break;
- default:
- plusSign = '\0';
- break;
- };
-
- double val = val_;
- switch (arg.presentation) {
- case '%':
- val *= 100;
- case 'f':
- case 'F':
- {
- if (arg.precision >
- DoubleToStringConverter::kMaxFixedDigitsAfterPoint) {
- arg.precision = DoubleToStringConverter::kMaxFixedDigitsAfterPoint;
- }
- DoubleToStringConverter conv(
- DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN,
- infinitySymbol,
- nanSymbol,
- exponentSymbol,
- -4, arg.precision,
- 0, 0);
- arg.enforce(conv.ToFixed(val, arg.precision, &builder),
- "fixed double conversion failed");
- }
- break;
- case 'e':
- case 'E':
- {
- if (arg.precision > DoubleToStringConverter::kMaxExponentialDigits) {
- arg.precision = DoubleToStringConverter::kMaxExponentialDigits;
- }
-
- DoubleToStringConverter conv(
- DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN,
- infinitySymbol,
- nanSymbol,
- exponentSymbol,
- -4, arg.precision,
- 0, 0);
- CHECK(conv.ToExponential(val, arg.precision, &builder));
- }
- break;
- case 'n': // should be locale-aware, but isn't
- case 'g':
- case 'G':
- {
- if (arg.precision < DoubleToStringConverter::kMinPrecisionDigits) {
- arg.precision = DoubleToStringConverter::kMinPrecisionDigits;
- } else if (arg.precision >
- DoubleToStringConverter::kMaxPrecisionDigits) {
- arg.precision = DoubleToStringConverter::kMaxPrecisionDigits;
- }
- DoubleToStringConverter conv(
- DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN,
- infinitySymbol,
- nanSymbol,
- exponentSymbol,
- -4, arg.precision,
- 0, 0);
- CHECK(conv.ToShortest(val, &builder));
- }
- break;
- default:
- LOG(FATAL) << arg.errorStr("invalid specifier '", arg.presentation, "'");
- }
-
- int len = builder.position();
- builder.Finalize();
- DCHECK_GT(len, 0);
-
- // Add '+' or ' ' sign if needed
- char* p = buf + 1;
- // anything that's neither negative nor nan
- int prefixLen = 0;
- if (plusSign && (*p != '-' && *p != 'n' && *p != 'N')) {
- *--p = plusSign;
- ++len;
- prefixLen = 1;
- } else if (*p == '-') {
- prefixLen = 1;
- }
-
- format_value::formatNumber(StringPiece(p, len), prefixLen, arg, cb);
+ fbstring piece;
+ int prefixLen;
+ formatHelper(piece, prefixLen, arg);
+ format_value::formatNumber(piece, prefixLen, arg, cb);
}
private:
+ void formatHelper(fbstring& piece, int& prefixLen, FormatArg& arg) const;
+
double val_;
};
void format(FormatArg& arg, FormatCallback& cb) const {
if (arg.keyEmpty()) {
arg.validate(FormatArg::Type::OTHER);
- CHECK(arg.presentation == FormatArg::kDefaultPresentation ||
- arg.presentation == 's')
- << arg.errorStr("invalid specifier '", arg.presentation, "'");
+ arg.enforce(arg.presentation == FormatArg::kDefaultPresentation ||
+ arg.presentation == 's',
+ "invalid specifier '", arg.presentation, "'");
format_value::formatString(val_, arg, cb);
} else {
FormatValue<char>(val_.at(arg.splitIntKey())).format(arg, cb);
template <class FormatCallback>
void format(FormatArg& arg, FormatCallback& cb) const {
arg.validate(FormatArg::Type::OTHER);
- CHECK(arg.presentation == FormatArg::kDefaultPresentation)
- << arg.errorStr("invalid specifier '", arg.presentation, "'");
+ arg.enforce(arg.presentation == FormatArg::kDefaultPresentation,
+ "invalid specifier '", arg.presentation, "'");
format_value::formatString("(null)", arg, cb);
}
};
} else {
// Print as a pointer, in hex.
arg.validate(FormatArg::Type::OTHER);
- CHECK(arg.presentation == FormatArg::kDefaultPresentation)
- << arg.errorStr("invalid specifier '", arg.presentation, "'");
+ arg.enforce(arg.presentation == FormatArg::kDefaultPresentation,
+ "invalid specifier '", arg.presentation, "'");
arg.basePrefix = true;
arg.presentation = 'x';
if (arg.align == FormatArg::Align::DEFAULT) {
public:
template <class FormatCallback>
static void formatOrFail(T& value, FormatArg& arg, FormatCallback& cb) {
- LOG(FATAL) << arg.errorStr("No formatter available for this type");
+ arg.error("No formatter available for this type");
}
};
namespace detail {
-// Shortcut, so we don't have to use enable_if everywhere
-struct FormatTraitsBase {
- typedef void enabled;
-};
-
-// Traits that define enabled, value_type, and at() for anything
-// indexable with integral keys: pointers, arrays, vectors, and maps
-// with integral keys
-template <class T, class Enable=void> struct IndexableTraits;
-
-// Base class for sequences (vectors, deques)
-template <class C>
-struct IndexableTraitsSeq : public FormatTraitsBase {
- typedef C container_type;
- typedef typename C::value_type value_type;
- static const value_type& at(const C& c, int idx) {
- return c.at(idx);
- }
-};
-
-// Base class for associative types (maps)
-template <class C>
-struct IndexableTraitsAssoc : public FormatTraitsBase {
- typedef typename C::value_type::second_type value_type;
- static const value_type& at(const C& c, int idx) {
- return c.at(static_cast<typename C::key_type>(idx));
- }
-};
-
// std::array
template <class T, size_t N>
struct IndexableTraits<std::array<T, N>>
: public IndexableTraitsSeq<std::deque<T, A>> {
};
-// fbvector
-template <class T, class A>
-struct IndexableTraits<fbvector<T, A>>
- : public IndexableTraitsSeq<fbvector<T, A>> {
-};
-
-// small_vector
-template <class T, size_t M, class A, class B, class C>
-struct IndexableTraits<small_vector<T, M, A, B, C>>
- : public IndexableTraitsSeq<small_vector<T, M, A, B, C>> {
-};
-
// std::map with integral keys
template <class K, class T, class C, class A>
struct IndexableTraits<
const T& val_;
};
+template <class Container, class Value>
+class FormatValue<
+ detail::DefaultValueWrapper<Container, Value>,
+ typename detail::IndexableTraits<Container>::enabled> {
+ public:
+ explicit FormatValue(const detail::DefaultValueWrapper<Container, Value>& val)
+ : val_(val) { }
+
+ template <class FormatCallback>
+ void format(FormatArg& arg, FormatCallback& cb) const {
+ FormatValue<typename std::decay<
+ typename detail::IndexableTraits<Container>::value_type>::type>(
+ detail::IndexableTraits<Container>::at(
+ val_.container,
+ arg.splitIntKey(),
+ val_.defaultValue)).format(arg, cb);
+ }
+
+ private:
+ const detail::DefaultValueWrapper<Container, Value>& val_;
+};
+
namespace detail {
// Define enabled, key_type, convert from StringPiece to the key types
static const value_type& at(const T& map, StringPiece key) {
return map.at(KeyFromStringPiece<key_type>::convert(key));
}
+ static const value_type& at(const T& map, StringPiece key,
+ const value_type& dflt) {
+ auto pos = map.find(KeyFromStringPiece<key_type>::convert(key));
+ return pos != map.end() ? pos->second : dflt;
+ }
};
// Define enabled, key_type, value_type, at() for supported string-keyed
const T& val_;
};
+template <class Container, class Value>
+class FormatValue<
+ detail::DefaultValueWrapper<Container, Value>,
+ typename detail::KeyableTraits<Container>::enabled> {
+ public:
+ explicit FormatValue(const detail::DefaultValueWrapper<Container, Value>& val)
+ : val_(val) { }
+
+ template <class FormatCallback>
+ void format(FormatArg& arg, FormatCallback& cb) const {
+ FormatValue<typename std::decay<
+ typename detail::KeyableTraits<Container>::value_type>::type>(
+ detail::KeyableTraits<Container>::at(
+ val_.container,
+ arg.splitKey(),
+ val_.defaultValue)).format(arg, cb);
+ }
+
+ private:
+ const detail::DefaultValueWrapper<Container, Value>& val_;
+};
+
// Partial specialization of FormatValue for pairs
template <class A, class B>
class FormatValue<std::pair<A, B>> {
FormatValue<typename std::decay<B>::type>(val_.second).format(arg, cb);
break;
default:
- LOG(FATAL) << arg.errorStr("invalid index for pair");
+ arg.error("invalid index for pair");
}
}
template <class FormatCallback>
void format(FormatArg& arg, FormatCallback& cb) const {
int key = arg.splitIntKey();
- CHECK(key >= 0) << arg.errorStr("tuple index must be non-negative");
+ arg.enforce(key >= 0, "tuple index must be non-negative");
doFormat(key, arg, cb);
}
template <size_t K, class Callback>
typename std::enable_if<K == valueCount>::type
doFormatFrom(size_t i, FormatArg& arg, Callback& cb) const {
- LOG(FATAL) << arg.errorStr("tuple index out of range, max=", i);
+ arg.enforce("tuple index out of range, max=", i);
}
template <size_t K, class Callback>
};
// Partial specialization of FormatValue for nested Formatters
-template <bool containerMode, class... Args>
-class FormatValue<Formatter<containerMode, Args...>, void> {
- typedef Formatter<containerMode, Args...> FormatterValue;
+template <bool containerMode, class... Args,
+ template <bool, class...> class F>
+class FormatValue<F<containerMode, Args...>,
+ typename std::enable_if<detail::IsFormatter<
+ F<containerMode, Args...>>::value>::type> {
+ typedef typename F<containerMode, Args...>::BaseType FormatterValue;
+
public:
explicit FormatValue(const FormatterValue& f) : f_(f) { }
* Formatter objects can be appended to strings, and therefore they're
* compatible with folly::toAppend and folly::to.
*/
-template <class Tgt, bool containerMode, class... Args>
-typename std::enable_if<
- detail::IsSomeString<Tgt>::value>::type
-toAppend(const Formatter<containerMode, Args...>& value, Tgt * result) {
+template <class Tgt, class Derived, bool containerMode, class... Args>
+typename std::enable_if<IsSomeString<Tgt>::value>::type toAppend(
+ const BaseFormatter<Derived, containerMode, Args...>& value, Tgt* result) {
value.appendTo(*result);
}
} // namespace folly
+
+#pragma GCC diagnostic pop