Support numeric types as targets for folly::split
authorMarcus Holland-Moritz <mhx@fb.com>
Mon, 10 Mar 2014 18:35:00 +0000 (11:35 -0700)
committerDave Watson <davejwatson@fb.com>
Mon, 10 Mar 2014 20:51:17 +0000 (13:51 -0700)
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

folly/String-inl.h
folly/String.h
folly/test/StringTest.cpp

index a2179209e4e240e6d83bf81f6da3550b6a3d342f..e680473b6b229c297efbd8d027e5aeb567388144 100644 (file)
@@ -347,25 +347,36 @@ template<class String> StringPiece prepareDelim(const String& s) {
 }
 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;
@@ -374,7 +385,7 @@ bool splitFixed(const Delim& delimiter,
   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;
@@ -423,11 +434,13 @@ void splitTo(const Delim& delimiter,
 
 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,
index 241d4dbf6aac26479d88853cf3514a4f99eac137..b5dc2417c2850840fe6749e70c459c1b742de912 100644 (file)
@@ -384,16 +384,24 @@ void splitTo(const Delim& delimiter,
              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
@@ -403,14 +411,24 @@ void splitTo(const Delim& delimiter,
  *  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.
index 925af1fe1de7ff6e68da533cbc06f9092cbe1939..cb3bcb7a6a991870acbbef1162e66741a7ba65e2 100644 (file)
@@ -834,6 +834,34 @@ TEST(Split, fixed) {
   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;