return result;
}
+namespace {
+/**
+ * StringPiece to double, with progress information. Alters the
+ * StringPiece parameter to munch the already-parsed characters.
+ */
+template <class Tgt>
+Tgt str_to_floating(StringPiece* src) {
+ using namespace double_conversion;
+ static StringToDoubleConverter
+ conv(StringToDoubleConverter::ALLOW_TRAILING_JUNK
+ | StringToDoubleConverter::ALLOW_LEADING_SPACES,
+ 0.0,
+ // return this for junk input string
+ std::numeric_limits<double>::quiet_NaN(),
+ nullptr, nullptr);
+
+ FOLLY_RANGE_CHECK_STRINGPIECE(!src->empty(),
+ "No digits found in input string", *src);
+
+ int length;
+ auto result = conv.StringToDouble(src->data(),
+ static_cast<int>(src->size()),
+ &length); // processed char count
+
+ if (!std::isnan(result)) {
+ src->advance(length);
+ return result;
+ }
+
+ for (;; src->advance(1)) {
+ if (src->empty()) {
+ throw std::range_error("Unable to convert an empty string"
+ " to a floating point value.");
+ }
+ if (!isspace(src->front())) {
+ break;
+ }
+ }
+
+ // Was that "inf[inity]"?
+ if (src->size() >= 3 && toupper((*src)[0]) == 'I'
+ && toupper((*src)[1]) == 'N' && toupper((*src)[2]) == 'F') {
+ if (src->size() >= 8 &&
+ toupper((*src)[3]) == 'I' &&
+ toupper((*src)[4]) == 'N' &&
+ toupper((*src)[5]) == 'I' &&
+ toupper((*src)[6]) == 'T' &&
+ toupper((*src)[7]) == 'Y') {
+ src->advance(8);
+ } else {
+ src->advance(3);
+ }
+ return std::numeric_limits<Tgt>::infinity();
+ }
+
+ // Was that "-inf[inity]"?
+ if (src->size() >= 4 && toupper((*src)[0]) == '-'
+ && toupper((*src)[1]) == 'I' && toupper((*src)[2]) == 'N'
+ && toupper((*src)[3]) == 'F') {
+ if (src->size() >= 9 &&
+ toupper((*src)[4]) == 'I' &&
+ toupper((*src)[5]) == 'N' &&
+ toupper((*src)[6]) == 'I' &&
+ toupper((*src)[7]) == 'T' &&
+ toupper((*src)[8]) == 'Y') {
+ src->advance(9);
+ } else {
+ src->advance(4);
+ }
+ return -std::numeric_limits<Tgt>::infinity();
+ }
+
+ // "nan"?
+ if (src->size() >= 3 && toupper((*src)[0]) == 'N'
+ && toupper((*src)[1]) == 'A' && toupper((*src)[2]) == 'N') {
+ src->advance(3);
+ return std::numeric_limits<Tgt>::quiet_NaN();
+ }
+
+ // "-nan"?
+ if (src->size() >= 4 &&
+ toupper((*src)[0]) == '-' &&
+ toupper((*src)[1]) == 'N' &&
+ toupper((*src)[2]) == 'A' &&
+ toupper((*src)[3]) == 'N') {
+ src->advance(4);
+ return -std::numeric_limits<Tgt>::quiet_NaN();
+ }
+
+ // All bets are off
+ throw std::range_error("Unable to convert \"" + src->toString()
+ + "\" to a floating point value.");
+}
+
+}
+
+float str_to_float(StringPiece* src) {
+ return str_to_floating<float>(src);
+}
+
+double str_to_double(StringPiece* src) {
+ return str_to_floating<double>(src);
+}
+
/**
* String represented as a pair of pointers to char to unsigned
* integrals. Assumes NO whitespace before or after, and also that the
// This is 20 * 8 == 160 bytes, which fits neatly into 5 cache lines
// (assuming a cache line size of 64).
static const uint64_t powersOf10[20] FOLLY_ALIGNED(64) = {
- 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000,
- 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000,
- 1000000000000000, 10000000000000000, 100000000000000000,
- 1000000000000000000, 10000000000000000000UL
+ 1,
+ 10,
+ 100,
+ 1000,
+ 10000,
+ 100000,
+ 1000000,
+ 10000000,
+ 100000000,
+ 1000000000,
+ 10000000000,
+ 100000000000,
+ 1000000000000,
+ 10000000000000,
+ 100000000000000,
+ 1000000000000000,
+ 10000000000000000,
+ 100000000000000000,
+ 1000000000000000000,
+ 10000000000000000000UL,
};
// "count leading zeroes" operation not valid; for 0; special case this.
* the space for them and will depend on strings exponential growth.
* If you just append once consider using toAppendFit which reserves
* the space needed (but does not have exponential as a result).
+ *
+ * Custom implementations of toAppend() can be provided in the same namespace as
+ * the type to customize printing. estimateSpaceNeed() may also be provided to
+ * avoid reallocations in toAppendFit():
+ *
+ * namespace other_namespace {
+ *
+ * template <class String>
+ * void toAppend(const OtherType&, String* out);
+ *
+ * // optional
+ * size_t estimateSpaceNeeded(const OtherType&);
+ *
+ * }
*/
template <class... Ts>
typename std::enable_if<sizeof...(Ts) >= 3
};
bool str_to_bool(StringPiece* src);
+ float str_to_float(StringPiece* src);
+ double str_to_double(StringPiece* src);
template <class Tgt>
Tgt digits_to(const char* b, const char* e);
extern template unsigned char digits_to<unsigned char>(const char* b,
- const char* e);
+ const char* e);
extern template unsigned short digits_to<unsigned short>(const char* b,
- const char* e);
+ const char* e);
extern template unsigned int digits_to<unsigned int>(const char* b,
- const char* e);
+ const char* e);
extern template unsigned long digits_to<unsigned long>(const char* b,
- const char* e);
+ const char* e);
extern template unsigned long long digits_to<unsigned long long>(
const char* b, const char* e);
#if FOLLY_HAVE_INT128_T
extern template unsigned __int128 digits_to<unsigned __int128>(const char* b,
- const char* e);
+ const char* e);
#endif
} // namespace detail
return result;
}
-/**
- * Parsing strings to integrals. These routines differ from
- * to<integral>(string) in that they take a POINTER TO a StringPiece
- * and alter that StringPiece to reflect progress information.
- */
-
+namespace detail {
/**
* StringPiece to integrals, with progress information. Alters the
* StringPiece parameter to munch the already-parsed characters.
*/
template <class Tgt>
-typename std::enable_if<
- std::is_integral<Tgt>::value
- && !std::is_same<typename std::remove_cv<Tgt>::type, bool>::value,
- Tgt>::type
-to(StringPiece * src) {
-
+Tgt str_to_integral(StringPiece* src) {
auto b = src->data(), past = src->data() + src->size();
for (;; ++b) {
FOLLY_RANGE_CHECK_STRINGPIECE(b < past,
return result;
}
-/**
- * StringPiece to bool, with progress information. Alters the
- * StringPiece parameter to munch the already-parsed characters.
- */
-template <class Tgt>
-typename std::enable_if<
- std::is_same<typename std::remove_cv<Tgt>::type, bool>::value,
- Tgt>::type
-to(StringPiece * src) {
- return detail::str_to_bool(src);
-}
-
-namespace detail {
-
/**
* Enforce that the suffix following a number is made up only of whitespace.
*/
-inline void enforceWhitespace(const char* b, const char* e) {
- for (; b != e; ++b) {
- FOLLY_RANGE_CHECK_BEGIN_END(isspace(*b),
- to<std::string>("Non-whitespace: ", *b),
- b, e);
+inline void enforceWhitespace(StringPiece sp) {
+ for (char ch : sp) {
+ FOLLY_RANGE_CHECK_STRINGPIECE(
+ isspace(ch), to<std::string>("Non-whitespace: ", ch), sp);
}
}
-} // namespace detail
-
-/**
- * String or StringPiece to integrals. Accepts leading and trailing
- * whitespace, but no non-space trailing characters.
- */
-template <class Tgt>
-typename std::enable_if<
- std::is_integral<Tgt>::value,
- Tgt>::type
-to(StringPiece src) {
- Tgt result = to<Tgt>(&src);
- detail::enforceWhitespace(src.data(), src.data() + src.size());
- return result;
-}
-
/*******************************************************************************
* Conversions from string types to floating-point types.
******************************************************************************/
+
+} // namespace detail
+
/**
- * StringPiece to double, with progress information. Alters the
+ * StringPiece to bool, with progress information. Alters the
* StringPiece parameter to munch the already-parsed characters.
*/
-template <class Tgt>
-inline typename std::enable_if<
- std::is_floating_point<Tgt>::value,
- Tgt>::type
-to(StringPiece *const src) {
- using namespace double_conversion;
- static StringToDoubleConverter
- conv(StringToDoubleConverter::ALLOW_TRAILING_JUNK
- | StringToDoubleConverter::ALLOW_LEADING_SPACES,
- 0.0,
- // return this for junk input string
- std::numeric_limits<double>::quiet_NaN(),
- nullptr, nullptr);
-
- FOLLY_RANGE_CHECK_STRINGPIECE(!src->empty(),
- "No digits found in input string", *src);
-
- int length;
- auto result = conv.StringToDouble(src->data(),
- static_cast<int>(src->size()),
- &length); // processed char count
-
- if (!std::isnan(result)) {
- src->advance(length);
- return result;
- }
-
- for (;; src->advance(1)) {
- if (src->empty()) {
- throw std::range_error("Unable to convert an empty string"
- " to a floating point value.");
- }
- if (!isspace(src->front())) {
- break;
- }
- }
-
- // Was that "inf[inity]"?
- if (src->size() >= 3 && toupper((*src)[0]) == 'I'
- && toupper((*src)[1]) == 'N' && toupper((*src)[2]) == 'F') {
- if (src->size() >= 8 &&
- toupper((*src)[3]) == 'I' &&
- toupper((*src)[4]) == 'N' &&
- toupper((*src)[5]) == 'I' &&
- toupper((*src)[6]) == 'T' &&
- toupper((*src)[7]) == 'Y') {
- src->advance(8);
- } else {
- src->advance(3);
- }
- return std::numeric_limits<Tgt>::infinity();
- }
-
- // Was that "-inf[inity]"?
- if (src->size() >= 4 && toupper((*src)[0]) == '-'
- && toupper((*src)[1]) == 'I' && toupper((*src)[2]) == 'N'
- && toupper((*src)[3]) == 'F') {
- if (src->size() >= 9 &&
- toupper((*src)[4]) == 'I' &&
- toupper((*src)[5]) == 'N' &&
- toupper((*src)[6]) == 'I' &&
- toupper((*src)[7]) == 'T' &&
- toupper((*src)[8]) == 'Y') {
- src->advance(9);
- } else {
- src->advance(4);
- }
- return -std::numeric_limits<Tgt>::infinity();
- }
+inline void parseTo(StringPiece* src, bool& out) {
+ out = detail::str_to_bool(src);
+}
- // "nan"?
- if (src->size() >= 3 && toupper((*src)[0]) == 'N'
- && toupper((*src)[1]) == 'A' && toupper((*src)[2]) == 'N') {
- src->advance(3);
- return std::numeric_limits<Tgt>::quiet_NaN();
- }
+/**
+ * Parsing strings to numeric types. These routines differ from
+ * parseTo(str, numeric) routines in that they take a POINTER TO a StringPiece
+ * and alter that StringPiece to reflect progress information.
+ */
+template <class Tgt>
+typename std::enable_if<
+ std::is_integral<typename std::remove_cv<Tgt>::type>::value>::type
+parseTo(StringPiece* src, Tgt& out) {
+ out = detail::str_to_integral<Tgt>(src);
+}
- // "-nan"?
- if (src->size() >= 4 &&
- toupper((*src)[0]) == '-' &&
- toupper((*src)[1]) == 'N' &&
- toupper((*src)[2]) == 'A' &&
- toupper((*src)[3]) == 'N') {
- src->advance(4);
- return -std::numeric_limits<Tgt>::quiet_NaN();
- }
+inline void parseTo(StringPiece* src, float& out) {
+ out = detail::str_to_float(src);
+}
- // All bets are off
- throw std::range_error("Unable to convert \"" + src->toString()
- + "\" to a floating point value.");
+inline void parseTo(StringPiece* src, double& out) {
+ out = detail::str_to_double(src);
}
-/**
- * Any string, const char*, or StringPiece to double.
- */
template <class Tgt>
typename std::enable_if<
- std::is_floating_point<Tgt>::value,
- Tgt>::type
-to(StringPiece src) {
- Tgt result = Tgt(to<double>(&src));
- detail::enforceWhitespace(src.data(), src.data() + src.size());
- return result;
+ std::is_floating_point<Tgt>::value ||
+ std::is_integral<typename std::remove_cv<Tgt>::type>::value>::type
+parseTo(StringPiece src, Tgt& out) {
+ parseTo(&src, out);
+ detail::enforceWhitespace(src);
}
/*******************************************************************************
return result;
}
+/*******************************************************************************
+ * Custom Conversions
+ *
+ * Any type can be used with folly::to by implementing parseTo. The
+ * implementation should be provided in the namespace of the type to facilitate
+ * argument-dependent lookup:
+ *
+ * namespace other_namespace {
+ * void parseTo(::folly::StringPiece, OtherType&);
+ * }
+ ******************************************************************************/
+template <class T>
+typename std::enable_if<std::is_enum<T>::value>::type
+parseTo(StringPiece in, T& out) {
+ typename std::underlying_type<T>::type tmp;
+ parseTo(in, tmp);
+ out = static_cast<T>(tmp);
+}
+
+inline void parseTo(StringPiece in, StringPiece& out) {
+ out = in;
+}
+
+inline void parseTo(StringPiece in, std::string& out) {
+ out.clear();
+ out.append(in.data(), in.size());
+}
+
+inline void parseTo(StringPiece in, fbstring& out) {
+ out.clear();
+ out.append(in.data(), in.size());
+}
+
+/**
+ * String or StringPiece to target conversion. Accepts leading and trailing
+ * whitespace, but no non-space trailing characters.
+ */
+
+template <class Tgt>
+typename std::enable_if<!std::is_same<StringPiece, Tgt>::value, Tgt>::type
+to(StringPiece src) {
+ Tgt result;
+ parseTo(src, result);
+ return result;
+}
+
+template <class Tgt>
+Tgt to(StringPiece* src) {
+ Tgt result;
+ parseTo(src, result);
+ return result;
+}
+
/*******************************************************************************
* Enum to anything and back
******************************************************************************/
return *s.start();
}
-/*
- * These output conversion templates allow us to support multiple
- * output string types, even when we are using an arbitrary
- * OutputIterator.
- */
-template<class OutStringT> struct OutputConverter {};
-
-template<> struct OutputConverter<std::string> {
- std::string operator()(StringPiece sp) const {
- return sp.toString();
- }
-};
-
-template<> struct OutputConverter<fbstring> {
- fbstring operator()(StringPiece sp) const {
- return sp.toFbstring();
- }
-};
-
-template<> struct OutputConverter<StringPiece> {
- StringPiece operator()(StringPiece sp) const { return sp; }
-};
-
/*
* Shared implementation for all the split() overloads.
*
const size_t strSize = sp.size();
const size_t dSize = delimSize(delim);
- OutputConverter<OutStringT> conv;
-
if (dSize > strSize || dSize == 0) {
if (!ignoreEmpty || strSize > 0) {
- *out++ = conv(sp);
+ *out++ = to<OutStringT>(sp);
}
return;
}
for (size_t i = 0; i <= strSize - dSize; ++i) {
if (atDelim(&s[i], delim)) {
if (!ignoreEmpty || tokenSize > 0) {
- *out++ = conv(StringPiece(&s[tokenStartPos], tokenSize));
+ *out++ = to<OutStringT>(sp.subpiece(tokenStartPos, tokenSize));
}
tokenStartPos = i + dSize;
}
tokenSize = strSize - tokenStartPos;
if (!ignoreEmpty || tokenSize > 0) {
- *out++ = conv(StringPiece(&s[tokenStartPos], tokenSize));
+ *out++ = to<OutStringT>(sp.subpiece(tokenStartPos, tokenSize));
}
}
}
inline char prepareDelim(char c) { return c; }
-template <class Dst>
-struct convertTo {
- template <class Src>
- static Dst from(const Src& src) { return folly::to<Dst>(src); }
- static Dst from(const Dst& src) { return src; }
-};
-
-template<bool exact,
- class Delim,
- class OutputType>
-typename std::enable_if<IsSplitTargetType<OutputType>::value, bool>::type
-splitFixed(const Delim& delimiter,
- StringPiece input,
- OutputType& out) {
+template <bool exact, class Delim, class OutputType>
+bool splitFixed(const Delim& delimiter, StringPiece input, OutputType& output) {
+ static_assert(
+ exact || std::is_same<OutputType, StringPiece>::value ||
+ IsSomeString<OutputType>::value,
+ "split<false>() requires that the last argument be a string type");
if (exact && UNLIKELY(std::string::npos != input.find(delimiter))) {
return false;
}
- out = convertTo<OutputType>::from(input);
+ parseTo(input, output);
return true;
}
-template<bool exact,
- class Delim,
- class OutputType,
- class... OutputTypes>
-typename std::enable_if<IsSplitTargetType<OutputType>::value, bool>::type
-splitFixed(const Delim& delimiter,
- StringPiece input,
- OutputType& outHead,
- OutputTypes&... outTail) {
+template <bool exact, class Delim, class OutputType, class... OutputTypes>
+bool splitFixed(
+ const Delim& delimiter,
+ StringPiece input,
+ OutputType& outHead,
+ OutputTypes&... outTail) {
size_t cut = input.find(delimiter);
if (UNLIKELY(cut == std::string::npos)) {
return false;
StringPiece tail(input.begin() + cut + detail::delimSize(delimiter),
input.end());
if (LIKELY(splitFixed<exact>(delimiter, tail, outTail...))) {
- outHead = convertTo<OutputType>::from(head);
+ parseTo(head, outHead);
return true;
}
return false;
ignoreEmpty);
}
-template<bool exact,
- class Delim,
- class OutputType,
- class... OutputTypes>
-typename std::enable_if<IsSplitTargetType<OutputType>::value, bool>::type
-split(const Delim& delimiter,
- StringPiece input,
- OutputType& outHead,
- OutputTypes&... outTail) {
+template <bool exact, class Delim, class... OutputTypes>
+typename std::enable_if<
+ AllConvertible<OutputTypes...>::value && sizeof...(OutputTypes) >= 1,
+ bool>::type
+split(const Delim& delimiter, StringPiece input, OutputTypes&... outputs) {
return detail::splitFixed<exact>(
- detail::prepareDelim(delimiter),
- input,
- outHead,
- outTail...);
+ detail::prepareDelim(delimiter), input, outputs...);
}
namespace detail {
double prettyToDouble(folly::StringPiece prettyString, const PrettyType type){
double result = prettyToDouble(&prettyString, type);
- detail::enforceWhitespace(prettyString.data(),
- prettyString.data() + prettyString.size());
+ detail::enforceWhitespace(prettyString);
return result;
}
void split(const Delim& delimiter,
const String& input,
std::vector<OutputType>& out,
- bool ignoreEmpty = false);
+ const bool ignoreEmpty = false);
template<class Delim, class String, class OutputType>
void split(const Delim& delimiter,
const String& input,
folly::fbvector<OutputType>& out,
- bool ignoreEmpty = false);
+ const bool ignoreEmpty = false);
template<class OutputValueType, class Delim, class String,
class OutputIterator>
void splitTo(const Delim& delimiter,
const String& input,
OutputIterator out,
- bool ignoreEmpty = false);
+ const bool ignoreEmpty = false);
/*
* Split a string into a fixed number of string pieces and/or numeric types
- * by delimiter. Any numeric type that folly::to<> can convert to from a
- * string piece is supported as a target. Returns 'true' if the fields were
- * all successfully populated. Returns 'false' if there were too few fields
- * in the input, or too many fields if exact=true. Casting exceptions will
- * not be caught.
+ * by delimiter. Conversions are supported for any type which folly:to<> can
+ * target, including all overloads of parseTo(). Returns 'true' if the fields
+ * were all successfully populated. Returns 'false' if there were too few
+ * fields in the input, or too many fields if exact=true. Casting exceptions
+ * will not be caught.
*
* Examples:
*
* Note that this will likely not work if the last field's target is of numeric
* type, in which case folly::to<> will throw an exception.
*/
+template <class T, class Enable = void>
+struct IsSomeVector {
+ enum { value = false };
+};
+
+template <class T>
+struct IsSomeVector<std::vector<T>, void> {
+ enum { value = true };
+};
+
template <class T>
-using IsSplitTargetType = std::integral_constant<bool,
- std::is_arithmetic<T>::value ||
- std::is_same<T, StringPiece>::value ||
- IsSomeString<T>::value>;
-
-template<bool exact = true,
- class Delim,
- class OutputType,
- class... OutputTypes>
-typename std::enable_if<IsSplitTargetType<OutputType>::value, bool>::type
-split(const Delim& delimiter,
- StringPiece input,
- OutputType& outHead,
- OutputTypes&... outTail);
+struct IsSomeVector<fbvector<T>, void> {
+ enum { value = true };
+};
+
+template <class T, class Enable = void>
+struct IsConvertible {
+ enum { value = false };
+};
+
+template <class T>
+struct IsConvertible<
+ T,
+ decltype(parseTo(std::declval<folly::StringPiece>(), std::declval<T&>()))> {
+ enum { value = true };
+};
+
+template <class... Types>
+struct AllConvertible;
+
+template <class Head, class... Tail>
+struct AllConvertible<Head, Tail...> {
+ enum { value = IsConvertible<Head>::value && AllConvertible<Tail...>::value };
+};
+
+template <>
+struct AllConvertible<> {
+ enum { value = true };
+};
+
+static_assert(AllConvertible<float>::value, "");
+static_assert(AllConvertible<int>::value, "");
+static_assert(AllConvertible<bool>::value, "");
+static_assert(AllConvertible<int>::value, "");
+static_assert(!AllConvertible<std::vector<int>>::value, "");
+
+template <bool exact = true, class Delim, class... OutputTypes>
+typename std::enable_if<
+ AllConvertible<OutputTypes...>::value && sizeof...(OutputTypes) >= 1,
+ bool>::type
+split(const Delim& delimiter, StringPiece input, OutputTypes&... outputs);
/*
* Join list of tokens.
auto u = to<uint32_t>(E::x);
EXPECT_GT(u, 0);
EXPECT_EQ(u, 3000000000U);
- auto s = to<string>(E::x);
- EXPECT_EQ("3000000000", s);
- auto e = to<E>(3000000000U);
- EXPECT_EQ(e, E::x);
- try {
- auto i = to<int32_t>(E::x);
- LOG(ERROR) << "to<int32_t> returned " << i << " instead of throwing";
- EXPECT_TRUE(false);
- } catch (std::range_error& e) {
- }
+ EXPECT_EQ("3000000000", to<string>(E::x));
+ EXPECT_EQ(E::x, to<E>(3000000000U));
+ EXPECT_EQ(E::x, to<E>("3000000000"));
+ E e;
+ parseTo("3000000000", e);
+ EXPECT_EQ(E::x, e);
+ EXPECT_THROW(to<int32_t>(E::x), std::range_error);
}
// Multi-argument to<string> uses toAppend, a different code path than
toAppendDelimFit(",", str1, str2, &res3);
EXPECT_EQ(res3, str1 + "," + str2);
}
+
+namespace my {
+struct Dimensions {
+ int w, h;
+ std::tuple<const int&, const int&> tuple_view() const {
+ return tie(w, h);
+ }
+ bool operator==(const Dimensions& other) const {
+ return this->tuple_view() == other.tuple_view();
+ }
+};
+
+void parseTo(folly::StringPiece in, Dimensions& out) {
+ out.w = folly::to<int>(&in);
+ in.removePrefix("x");
+ out.h = folly::to<int>(&in);
+}
+
+template <class String>
+void toAppend(const Dimensions& in, String* result) {
+ folly::toAppend(in.w, 'x', in.h, result);
+}
+
+size_t estimateSpaceNeeded(const Dimensions&in) {
+ return 2000 + folly::estimateSpaceNeeded(in.w) +
+ folly::estimateSpaceNeeded(in.h);
+}
+}
+
+TEST(Conv, custom_kkproviders) {
+ my::Dimensions expected{7, 8};
+ EXPECT_EQ(expected, folly::to<my::Dimensions>("7x8"));
+ auto str = folly::to<std::string>(expected);
+ EXPECT_EQ("7x8", str);
+ // make sure above implementation of estimateSpaceNeeded() is used.
+ EXPECT_GT(str.capacity(), 2000);
+ EXPECT_LT(str.capacity(), 2500);
+}
EXPECT_EQ(13, b);
EXPECT_EQ("14.7:b", d);
- EXPECT_THROW(folly::split<false>(':', "a:13:14.7:b", a, b, c),
- std::range_error);
+
+ // Enable verifying that a line only contains one field
+ EXPECT_TRUE(folly::split(' ', "hello", a));
+ EXPECT_FALSE(folly::split(' ', "hello world", a));
+}
+
+namespace my {
+
+enum class Color {
+ Red,
+ Blue,
+};
+
+void parseTo(folly::StringPiece in, Color& out) {
+ if (in == "R") {
+ out = Color::Red;
+ } else if (in == "B") {
+ out = Color::Blue;
+ } else {
+ throw runtime_error("");
+ }
+}
+}
+
+TEST(Split, fixed_convert_custom) {
+ my::Color c1, c2;
+
+ EXPECT_TRUE(folly::split(',', "R,B", c1, c2));
+ EXPECT_EQ(c1, my::Color::Red);
+ EXPECT_EQ(c2, my::Color::Blue);
}
TEST(String, join) {