Commit 54f2a4c8 authored by Tom Jackson's avatar Tom Jackson Committed by Facebook Github Bot 9

Extensibility for folly::to<> through ADL

Summary: Primarily to support slightly more flexible implementations of `split()`;

Reviewed By: ot

Differential Revision: D3116763

fb-gh-sync-id: 69023c0f26058516f25b9c1f9824055efc7021f9
fbshipit-source-id: 69023c0f26058516f25b9c1f9824055efc7021f9
parent 4785dfe0
......@@ -266,6 +266,110 @@ bool str_to_bool(StringPiece* src) {
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 diff is collapsed.
......@@ -263,29 +263,6 @@ inline char delimFront(StringPiece s) {
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.
*
......@@ -304,11 +281,9 @@ void internalSplit(DelimT delim, StringPiece sp, OutputIterator out,
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;
}
......@@ -323,7 +298,7 @@ void internalSplit(DelimT delim, StringPiece sp, OutputIterator out,
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;
......@@ -335,7 +310,7 @@ void internalSplit(DelimT delim, StringPiece sp, OutputIterator out,
}
tokenSize = strSize - tokenStartPos;
if (!ignoreEmpty || tokenSize > 0) {
*out++ = conv(StringPiece(&s[tokenStartPos], tokenSize));
*out++ = to<OutStringT>(sp.subpiece(tokenStartPos, tokenSize));
}
}
......@@ -344,36 +319,25 @@ 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,
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;
......@@ -382,7 +346,7 @@ splitFixed(const Delim& delimiter,
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;
......@@ -429,20 +393,13 @@ void splitTo(const Delim& delimiter,
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 {
......
......@@ -306,8 +306,7 @@ double prettyToDouble(folly::StringPiece *const prettyString,
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;
}
......
......@@ -432,28 +432,28 @@ template<class Delim, class String, class OutputType>
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:
*
......@@ -481,21 +481,57 @@ void splitTo(const Delim& delimiter,
* 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.
......
......@@ -699,16 +699,13 @@ TEST(Conv, UnsignedEnumClass) {
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
......@@ -848,3 +845,41 @@ TEST(Conv, allocate_size) {
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);
}
......@@ -906,8 +906,36 @@ TEST(Split, fixed_convert) {
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) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment