Summary:
This extends the fixed version of folly::split to support numeric types
as targets in addition to string pieces. I was almost certain this was
already possible when I recently reviewed some code that did
folly::StringPiece source, target, strFrequency;
int frequency;
if (folly::split('\t', line, source, target, strFrequency) &&
(frequency = folly::to<unsigned int>(strFrequency)) > 0)
and was about to suggest changing the above into:
folly::StringPiece source, target;
int frequency;
if (folly::split('\t', line, source, target, frequency) && frequency > 0)
I double checked and saw that only splitting to string pieces was supported.
In the meantime I came across this pattern again and decided to just make
it work because it's really convenient.
The implementation should be fully backwards compatible.
Test Plan:
- New unit tests
- fbconfig -r folly && fbmake runtests
- Applied to github release, ./configure && make check
Reviewed By: andrei.alexandrescu@fb.com
FB internal diff:
D1187004
}
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>
-bool splitFixed(const Delim& delimiter,
- StringPiece input,
- StringPiece& out) {
+ class Delim,
+ class OutputType>
+typename std::enable_if<IsSplitTargetType<OutputType>::value, bool>::type
+splitFixed(const Delim& delimiter,
+ StringPiece input,
+ OutputType& out) {
if (exact && UNLIKELY(std::string::npos != input.find(delimiter))) {
return false;
}
- out = input;
+ out = convertTo<OutputType>::from(input);
return true;
}
template<bool exact,
class Delim,
- class... StringPieces>
-bool splitFixed(const Delim& delimiter,
- StringPiece input,
- StringPiece& outHead,
- StringPieces&... outTail) {
+ class OutputType,
+ class... OutputTypes>
+typename std::enable_if<IsSplitTargetType<OutputType>::value, bool>::type
+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 = head;
+ outHead = convertTo<OutputType>::from(head);
return true;
}
return false;
template<bool exact,
class Delim,
- class... StringPieces>
-bool split(const Delim& delimiter,
- StringPiece input,
- StringPiece& outHead,
- StringPieces&... outTail) {
+ class OutputType,
+ class... OutputTypes>
+typename std::enable_if<IsSplitTargetType<OutputType>::value, bool>::type
+split(const Delim& delimiter,
+ StringPiece input,
+ OutputType& outHead,
+ OutputTypes&... outTail) {
return detail::splitFixed<exact>(
detail::prepareDelim(delimiter),
input,
bool ignoreEmpty = false);
/*
- * Split a string into a fixed number of pieces by delimiter. Returns 'true' if
- * the fields were all successfully populated.
+ * 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.
*
- * Example:
+ * Examples:
*
* folly::StringPiece name, key, value;
* if (folly::split('\t', line, name, key, value))
* ...
*
- * The 'exact' template paremeter specifies how the function behaves when too
+ * folly::StringPiece name;
+ * double value;
+ * int id;
+ * if (folly::split('\t', line, name, value, id))
+ * ...
+ *
+ * The 'exact' template parameter specifies how the function behaves when too
* many fields are present in the input string. When 'exact' is set to its
* default value of 'true', a call to split will fail if the number of fields in
* the input string does not exactly match the number of output parameters
* folly::StringPiece x, y.
* if (folly::split<false>(':', "a:b:c", x, y))
* assert(x == "a" && y == "b:c");
+ *
+ * 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>
+using IsSplitTargetType = std::integral_constant<bool,
+ std::is_arithmetic<T>::value ||
+ std::is_same<T, StringPiece>::value>;
+
template<bool exact = true,
class Delim,
- class... StringPieces>
-bool split(const Delim& delimiter,
- StringPiece input,
- StringPiece& outHead,
- StringPieces&... outTail);
+ class OutputType,
+ class... OutputTypes>
+typename std::enable_if<IsSplitTargetType<OutputType>::value, bool>::type
+split(const Delim& delimiter,
+ StringPiece input,
+ OutputType& outHead,
+ OutputTypes&... outTail);
/*
* Join list of tokens.
EXPECT_FALSE(folly::split('.', "a.b", a));
}
+TEST(Split, fixed_convert) {
+ StringPiece a, d;
+ int b;
+ double c;
+
+ EXPECT_TRUE(folly::split(':', "a:13:14.7:b", a, b, c, d));
+ EXPECT_EQ("a", a);
+ EXPECT_EQ(13, b);
+ EXPECT_NEAR(14.7, c, 1e-10);
+ EXPECT_EQ("b", d);
+
+ EXPECT_TRUE(folly::split<false>(':', "b:14:15.3:c", a, b, c, d));
+ EXPECT_EQ("b", a);
+ EXPECT_EQ(14, b);
+ EXPECT_NEAR(15.3, c, 1e-10);
+ EXPECT_EQ("c", d);
+
+ EXPECT_FALSE(folly::split(':', "a:13:14.7:b", a, b, d));
+
+ EXPECT_TRUE(folly::split<false>(':', "a:13:14.7:b", a, b, d));
+ EXPECT_EQ("a", a);
+ 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);
+}
+
TEST(String, join) {
string output;