Commit 89cc3293 authored by Eric Niebler's avatar Eric Niebler Committed by Facebook Github Bot 5

Use folly::Expected to implement folly::tryTo, a non-throwing variant of folly::to

Summary:
This change adds a non-throwing interface for folly::to<T>: tryTo<T>, which
returns an Expected<T, ConversionCode>.

Here is how the non-throwing interface compares to the regular interface in
terms of performance. On the successful path, there's generally not much
difference between using the throwing and non-throwing interfaces. For the
error path, tryTo<> is about three orders of magnitude faster than to<>.

Reviewed By: mhx

Differential Revision: D3557832

fbshipit-source-id: 25b29834b6fda474a7cdd43a7dea69ff4ee183c8
parent ae7eed06
...@@ -207,8 +207,10 @@ struct ErrorString { ...@@ -207,8 +207,10 @@ struct ErrorString {
bool quote; bool quote;
}; };
// Keep this in sync with ConversionError::Code in Conv.h // Keep this in sync with ConversionCode in Conv.h
constexpr const std::array<ErrorString, ConversionError::NUM_ERROR_CODES> constexpr const std::array<
ErrorString,
static_cast<std::size_t>(ConversionCode::NUM_ERROR_CODES)>
kErrorStrings{{ kErrorStrings{{
{"Success", true}, {"Success", true},
{"Empty input string", true}, {"Empty input string", true},
...@@ -255,34 +257,11 @@ inline bool bool_str_cmp(const char** b, size_t len, const char* value) { ...@@ -255,34 +257,11 @@ inline bool bool_str_cmp(const char** b, size_t len, const char* value) {
} // anonymous namespace } // anonymous namespace
ConversionError makeConversionError( Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept {
ConversionError::Code code,
const char* input,
size_t inputLen) {
assert(code >= 0 && code < kErrorStrings.size());
const ErrorString& err = kErrorStrings[code];
if (code == ConversionError::EMPTY_INPUT_STRING && inputLen == 0) {
return ConversionError(err.string, code);
}
std::string tmp(err.string);
tmp.append(": ");
if (err.quote) {
tmp.append(1, '"');
}
if (input && inputLen > 0) {
tmp.append(input, inputLen);
}
if (err.quote) {
tmp.append(1, '"');
}
return ConversionError(tmp, code);
}
ConversionResult<bool> str_to_bool(StringPiece* src) {
auto b = src->begin(), e = src->end(); auto b = src->begin(), e = src->end();
for (;; ++b) { for (;; ++b) {
if (b >= e) { if (b >= e) {
return ConversionResult<bool>(ConversionError::EMPTY_INPUT_STRING); return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
} }
if (!std::isspace(*b)) { if (!std::isspace(*b)) {
break; break;
...@@ -297,7 +276,7 @@ ConversionResult<bool> str_to_bool(StringPiece* src) { ...@@ -297,7 +276,7 @@ ConversionResult<bool> str_to_bool(StringPiece* src) {
result = false; result = false;
for (; b < e && isdigit(*b); ++b) { for (; b < e && isdigit(*b); ++b) {
if (result || (*b != '0' && *b != '1')) { if (result || (*b != '0' && *b != '1')) {
return ConversionResult<bool>(ConversionError::BOOL_OVERFLOW); return makeUnexpected(ConversionCode::BOOL_OVERFLOW);
} }
result = (*b == '1'); result = (*b == '1');
} }
...@@ -338,16 +317,16 @@ ConversionResult<bool> str_to_bool(StringPiece* src) { ...@@ -338,16 +317,16 @@ ConversionResult<bool> str_to_bool(StringPiece* src) {
} else if (bool_str_cmp(&b, len, "off")) { } else if (bool_str_cmp(&b, len, "off")) {
result = false; result = false;
} else { } else {
return ConversionResult<bool>(ConversionError::BOOL_INVALID_VALUE); return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE);
} }
break; break;
default: default:
return ConversionResult<bool>(ConversionError::BOOL_INVALID_VALUE); return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE);
} }
src->assign(b, e); src->assign(b, e);
return ConversionResult<bool>(result); return result;
} }
/** /**
...@@ -355,7 +334,7 @@ ConversionResult<bool> str_to_bool(StringPiece* src) { ...@@ -355,7 +334,7 @@ ConversionResult<bool> str_to_bool(StringPiece* src) {
* StringPiece parameter to munch the already-parsed characters. * StringPiece parameter to munch the already-parsed characters.
*/ */
template <class Tgt> template <class Tgt>
ConversionResult<Tgt> str_to_floating(StringPiece* src) { Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept {
using namespace double_conversion; using namespace double_conversion;
static StringToDoubleConverter static StringToDoubleConverter
conv(StringToDoubleConverter::ALLOW_TRAILING_JUNK conv(StringToDoubleConverter::ALLOW_TRAILING_JUNK
...@@ -366,7 +345,7 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) { ...@@ -366,7 +345,7 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) {
nullptr, nullptr); nullptr, nullptr);
if (src->empty()) { if (src->empty()) {
return ConversionResult<Tgt>(ConversionError::EMPTY_INPUT_STRING); return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
} }
int length; int length;
...@@ -383,10 +362,10 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) { ...@@ -383,10 +362,10 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) {
// that was processed, so we need to check if that character was // that was processed, so we need to check if that character was
// whitespace or not. // whitespace or not.
if (length == 0 || (result == 0.0 && std::isspace((*src)[length - 1]))) { if (length == 0 || (result == 0.0 && std::isspace((*src)[length - 1]))) {
return ConversionResult<Tgt>(ConversionError::EMPTY_INPUT_STRING); return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
} }
src->advance(length); src->advance(length);
return ConversionResult<Tgt>(result); return result;
} }
auto* e = src->end(); auto* e = src->end();
...@@ -435,7 +414,7 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) { ...@@ -435,7 +414,7 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) {
if (result == 0.0) { if (result == 0.0) {
// All bets are off // All bets are off
return ConversionResult<Tgt>(ConversionError::STRING_TO_FLOAT_ERROR); return makeUnexpected(ConversionCode::STRING_TO_FLOAT_ERROR);
} }
if (negative) { if (negative) {
...@@ -444,11 +423,13 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) { ...@@ -444,11 +423,13 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) {
src->assign(b, e); src->assign(b, e);
return ConversionResult<Tgt>(result); return result;
} }
template ConversionResult<float> str_to_floating<float>(StringPiece* src); template Expected<float, ConversionCode> str_to_floating<float>(
template ConversionResult<double> str_to_floating<double>(StringPiece* src); StringPiece* src) noexcept;
template Expected<double, ConversionCode> str_to_floating<double>(
StringPiece* src) noexcept;
/** /**
* This class takes care of additional processing needed for signed values, * This class takes care of additional processing needed for signed values,
...@@ -460,39 +441,39 @@ class SignedValueHandler; ...@@ -460,39 +441,39 @@ class SignedValueHandler;
template <typename T> template <typename T>
class SignedValueHandler<T, true> { class SignedValueHandler<T, true> {
public: public:
ConversionError::Code init(const char*& b) { ConversionCode init(const char*& b) {
negative_ = false; negative_ = false;
if (!std::isdigit(*b)) { if (!std::isdigit(*b)) {
if (*b == '-') { if (*b == '-') {
negative_ = true; negative_ = true;
} else if (UNLIKELY(*b != '+')) { } else if (UNLIKELY(*b != '+')) {
return ConversionError::INVALID_LEADING_CHAR; return ConversionCode::INVALID_LEADING_CHAR;
} }
++b; ++b;
} }
return ConversionError::SUCCESS; return ConversionCode::SUCCESS;
} }
ConversionError::Code overflow() { ConversionCode overflow() {
return negative_ ? ConversionError::NEGATIVE_OVERFLOW return negative_ ? ConversionCode::NEGATIVE_OVERFLOW
: ConversionError::POSITIVE_OVERFLOW; : ConversionCode::POSITIVE_OVERFLOW;
} }
template <typename U> template <typename U>
ConversionResult<T> finalize(U value) { Expected<T, ConversionCode> finalize(U value) {
T rv; T rv;
if (negative_) { if (negative_) {
rv = -value; rv = -value;
if (UNLIKELY(rv > 0)) { if (UNLIKELY(rv > 0)) {
return ConversionResult<T>(ConversionError::NEGATIVE_OVERFLOW); return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
} }
} else { } else {
rv = value; rv = value;
if (UNLIKELY(rv < 0)) { if (UNLIKELY(rv < 0)) {
return ConversionResult<T>(ConversionError::POSITIVE_OVERFLOW); return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
} }
} }
return ConversionResult<T>(rv); return rv;
} }
private: private:
...@@ -503,16 +484,16 @@ class SignedValueHandler<T, true> { ...@@ -503,16 +484,16 @@ class SignedValueHandler<T, true> {
template <typename T> template <typename T>
class SignedValueHandler<T, false> { class SignedValueHandler<T, false> {
public: public:
ConversionError::Code init(const char*&) { ConversionCode init(const char*&) {
return ConversionError::SUCCESS; return ConversionCode::SUCCESS;
} }
ConversionError::Code overflow() { ConversionCode overflow() {
return ConversionError::POSITIVE_OVERFLOW; return ConversionCode::POSITIVE_OVERFLOW;
} }
ConversionResult<T> finalize(T value) { Expected<T, ConversionCode> finalize(T value) {
return ConversionResult<T>(value); return value;
} }
}; };
...@@ -524,15 +505,17 @@ class SignedValueHandler<T, false> { ...@@ -524,15 +505,17 @@ class SignedValueHandler<T, false> {
* an appropriate error. * an appropriate error.
*/ */
template <class Tgt> template <class Tgt>
inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) { inline Expected<Tgt, ConversionCode> digits_to(
const char* b,
const char* const e) noexcept {
using UT = typename std::make_unsigned<Tgt>::type; using UT = typename std::make_unsigned<Tgt>::type;
assert(b <= e); assert(b <= e);
SignedValueHandler<Tgt> sgn; SignedValueHandler<Tgt> sgn;
auto err = sgn.init(b); auto err = sgn.init(b);
if (UNLIKELY(err != ConversionError::SUCCESS)) { if (UNLIKELY(err != ConversionCode::SUCCESS)) {
return ConversionResult<Tgt>(err); return makeUnexpected(err);
} }
size_t size = e - b; size_t size = e - b;
...@@ -545,7 +528,7 @@ inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) { ...@@ -545,7 +528,7 @@ inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) {
if (b < e && *b == '0') { if (b < e && *b == '0') {
for (++b;; ++b) { for (++b;; ++b) {
if (b == e) { if (b == e) {
return ConversionResult<Tgt>(Tgt(0)); // just zeros, e.g. "0000" return Tgt(0); // just zeros, e.g. "0000"
} }
if (*b != '0') { if (*b != '0') {
size = e - b; size = e - b;
...@@ -556,7 +539,7 @@ inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) { ...@@ -556,7 +539,7 @@ inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) {
if (size > std::numeric_limits<UT>::digits10 && if (size > std::numeric_limits<UT>::digits10 &&
(size != std::numeric_limits<UT>::digits10 + 1 || (size != std::numeric_limits<UT>::digits10 + 1 ||
strncmp(b, MaxString<UT>::value, size) > 0)) { strncmp(b, MaxString<UT>::value, size) > 0)) {
return ConversionResult<Tgt>(sgn.overflow()); return makeUnexpected(sgn.overflow());
} }
} }
...@@ -611,7 +594,7 @@ inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) { ...@@ -611,7 +594,7 @@ inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) {
default: default:
assert(b == e); assert(b == e);
if (size == 0) { if (size == 0) {
return ConversionResult<Tgt>(ConversionError::NO_DIGITS); return makeUnexpected(ConversionCode::NO_DIGITS);
} }
break; break;
} }
...@@ -619,46 +602,52 @@ inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) { ...@@ -619,46 +602,52 @@ inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) {
return sgn.finalize(result); return sgn.finalize(result);
outOfRange: outOfRange:
return ConversionResult<Tgt>(ConversionError::NON_DIGIT_CHAR); return makeUnexpected(ConversionCode::NON_DIGIT_CHAR);
} }
template ConversionResult<char> digits_to<char>(const char*, const char*); template Expected<char, ConversionCode> digits_to<char>(
template ConversionResult<signed char> digits_to<signed char>(
const char*, const char*,
const char*); const char*) noexcept;
template ConversionResult<unsigned char> digits_to<unsigned char>( template Expected<signed char, ConversionCode> digits_to<signed char>(
const char*, const char*,
const char*); const char*) noexcept;
template Expected<unsigned char, ConversionCode> digits_to<unsigned char>(
template ConversionResult<short> digits_to<short>(const char*, const char*);
template ConversionResult<unsigned short> digits_to<unsigned short>(
const char*, const char*,
const char*); const char*) noexcept;
template ConversionResult<int> digits_to<int>(const char*, const char*); template Expected<short, ConversionCode> digits_to<short>(
template ConversionResult<unsigned int> digits_to<unsigned int>(
const char*, const char*,
const char*); const char*) noexcept;
template Expected<unsigned short, ConversionCode> digits_to<unsigned short>(
const char*,
const char*) noexcept;
template ConversionResult<long> digits_to<long>(const char*, const char*); template Expected<int, ConversionCode> digits_to<int>(
template ConversionResult<unsigned long> digits_to<unsigned long>(
const char*, const char*,
const char*); const char*) noexcept;
template Expected<unsigned int, ConversionCode> digits_to<unsigned int>(
const char*,
const char*) noexcept;
template ConversionResult<long long> digits_to<long long>( template Expected<long, ConversionCode> digits_to<long>(
const char*, const char*,
const char*); const char*);
template ConversionResult<unsigned long long> digits_to<unsigned long long>( template Expected<unsigned long, ConversionCode> digits_to<unsigned long>(
const char*, const char*,
const char*); const char*) noexcept;
#if FOLLY_HAVE_INT128_T template Expected<long long, ConversionCode> digits_to<long long>(
template ConversionResult<__int128> digits_to<__int128>(
const char*, const char*,
const char*); const char*) noexcept;
template ConversionResult<unsigned __int128> digits_to<unsigned __int128>( template Expected<unsigned long long, ConversionCode>
digits_to<unsigned long long>(const char*, const char*) noexcept;
#if FOLLY_HAVE_INT128_T
template Expected<__int128, ConversionCode> digits_to<__int128>(
const char*, const char*,
const char*); const char*) noexcept;
template Expected<unsigned __int128, ConversionCode>
digits_to<unsigned __int128>(const char*, const char*) noexcept;
#endif #endif
/** /**
...@@ -666,14 +655,14 @@ template ConversionResult<unsigned __int128> digits_to<unsigned __int128>( ...@@ -666,14 +655,14 @@ template ConversionResult<unsigned __int128> digits_to<unsigned __int128>(
* StringPiece parameter to munch the already-parsed characters. * StringPiece parameter to munch the already-parsed characters.
*/ */
template <class Tgt> template <class Tgt>
ConversionResult<Tgt> str_to_integral(StringPiece* src) { Expected<Tgt, ConversionCode> str_to_integral(StringPiece* src) noexcept {
using UT = typename std::make_unsigned<Tgt>::type; using UT = typename std::make_unsigned<Tgt>::type;
auto b = src->data(), past = src->data() + src->size(); auto b = src->data(), past = src->data() + src->size();
for (;; ++b) { for (;; ++b) {
if (UNLIKELY(b >= past)) { if (UNLIKELY(b >= past)) {
return ConversionResult<Tgt>(ConversionError::EMPTY_INPUT_STRING); return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
} }
if (!std::isspace(*b)) { if (!std::isspace(*b)) {
break; break;
...@@ -683,63 +672,93 @@ ConversionResult<Tgt> str_to_integral(StringPiece* src) { ...@@ -683,63 +672,93 @@ ConversionResult<Tgt> str_to_integral(StringPiece* src) {
SignedValueHandler<Tgt> sgn; SignedValueHandler<Tgt> sgn;
auto err = sgn.init(b); auto err = sgn.init(b);
if (UNLIKELY(err != ConversionError::SUCCESS)) { if (UNLIKELY(err != ConversionCode::SUCCESS)) {
return ConversionResult<Tgt>(err); return makeUnexpected(err);
} }
if (std::is_signed<Tgt>::value && UNLIKELY(b >= past)) { if (std::is_signed<Tgt>::value && UNLIKELY(b >= past)) {
return ConversionResult<Tgt>(ConversionError::NO_DIGITS); return makeUnexpected(ConversionCode::NO_DIGITS);
} }
if (UNLIKELY(!isdigit(*b))) { if (UNLIKELY(!isdigit(*b))) {
return ConversionResult<Tgt>(ConversionError::NON_DIGIT_CHAR); return makeUnexpected(ConversionCode::NON_DIGIT_CHAR);
} }
auto m = findFirstNonDigit(b + 1, past); auto m = findFirstNonDigit(b + 1, past);
auto tmp = digits_to<UT>(b, m); auto tmp = digits_to<UT>(b, m);
if (UNLIKELY(!tmp.success())) { if (UNLIKELY(!tmp.hasValue())) {
return ConversionResult<Tgt>( return makeUnexpected(
tmp.error == ConversionError::POSITIVE_OVERFLOW ? sgn.overflow() tmp.error() == ConversionCode::POSITIVE_OVERFLOW ? sgn.overflow()
: tmp.error); : tmp.error());
} }
auto res = sgn.finalize(tmp.value); auto res = sgn.finalize(tmp.value());
if (res.success()) { if (res.hasValue()) {
src->advance(m - src->data()); src->advance(m - src->data());
} }
return res; return res;
} }
template ConversionResult<char> str_to_integral<char>(StringPiece* src); template Expected<char, ConversionCode> str_to_integral<char>(
template ConversionResult<signed char> str_to_integral<signed char>( StringPiece* src) noexcept;
StringPiece* src); template Expected<signed char, ConversionCode> str_to_integral<signed char>(
template ConversionResult<unsigned char> str_to_integral<unsigned char>( StringPiece* src) noexcept;
StringPiece* src); template Expected<unsigned char, ConversionCode> str_to_integral<unsigned char>(
StringPiece* src) noexcept;
template ConversionResult<short> str_to_integral<short>(StringPiece* src);
template ConversionResult<unsigned short> str_to_integral<unsigned short>( template Expected<short, ConversionCode> str_to_integral<short>(
StringPiece* src); StringPiece* src) noexcept;
template Expected<unsigned short, ConversionCode>
template ConversionResult<int> str_to_integral<int>(StringPiece* src); str_to_integral<unsigned short>(StringPiece* src) noexcept;
template ConversionResult<unsigned int> str_to_integral<unsigned int>(
StringPiece* src); template Expected<int, ConversionCode> str_to_integral<int>(
StringPiece* src) noexcept;
template ConversionResult<long> str_to_integral<long>(StringPiece* src); template Expected<unsigned int, ConversionCode> str_to_integral<unsigned int>(
template ConversionResult<unsigned long> str_to_integral<unsigned long>( StringPiece* src) noexcept;
StringPiece* src);
template Expected<long, ConversionCode> str_to_integral<long>(
template ConversionResult<long long> str_to_integral<long long>( StringPiece* src) noexcept;
StringPiece* src); template Expected<unsigned long, ConversionCode> str_to_integral<unsigned long>(
template ConversionResult<unsigned long long> StringPiece* src) noexcept;
str_to_integral<unsigned long long>(StringPiece* src);
template Expected<long long, ConversionCode> str_to_integral<long long>(
StringPiece* src) noexcept;
template Expected<unsigned long long, ConversionCode>
str_to_integral<unsigned long long>(StringPiece* src) noexcept;
#if FOLLY_HAVE_INT128_T #if FOLLY_HAVE_INT128_T
template ConversionResult<__int128> str_to_integral<__int128>(StringPiece* src); template Expected<__int128, ConversionCode> str_to_integral<__int128>(
template ConversionResult<unsigned __int128> str_to_integral<unsigned __int128>( StringPiece* src) noexcept;
StringPiece* src); template Expected<unsigned __int128, ConversionCode>
str_to_integral<unsigned __int128>(StringPiece* src) noexcept;
#endif #endif
} // namespace detail } // namespace detail
ConversionError makeConversionError(ConversionCode code, StringPiece input) {
using namespace detail;
static_assert(
std::is_unsigned<std::underlying_type<ConversionCode>::type>::value,
"ConversionCode should be unsigned");
assert((std::size_t)code < kErrorStrings.size());
const ErrorString& err = kErrorStrings[(std::size_t)code];
if (code == ConversionCode::EMPTY_INPUT_STRING && input.empty()) {
return {err.string, code};
}
std::string tmp(err.string);
tmp.append(": ");
if (err.quote) {
tmp.append(1, '"');
}
if (input.size() > 0) {
tmp.append(input.data(), input.size());
}
if (err.quote) {
tmp.append(1, '"');
}
return {tmp, code};
}
} // namespace folly } // namespace folly
...@@ -39,75 +39,88 @@ ...@@ -39,75 +39,88 @@
#include <double-conversion/double-conversion.h> // V8 JavaScript implementation #include <double-conversion/double-conversion.h> // V8 JavaScript implementation
#include <folly/Demangle.h> #include <folly/Demangle.h>
#include <folly/Expected.h>
#include <folly/FBString.h> #include <folly/FBString.h>
#include <folly/Likely.h> #include <folly/Likely.h>
#include <folly/Range.h> #include <folly/Range.h>
#include <folly/Unit.h>
#include <folly/portability/Math.h> #include <folly/portability/Math.h>
namespace folly { namespace folly {
class ConversionError : public std::range_error { // Keep this in sync with kErrorStrings in Conv.cpp
public: enum class ConversionCode : unsigned char {
// Keep this in sync with kErrorStrings in Conv.cpp SUCCESS,
enum Code { EMPTY_INPUT_STRING,
SUCCESS, NO_DIGITS,
EMPTY_INPUT_STRING, BOOL_OVERFLOW,
NO_DIGITS, BOOL_INVALID_VALUE,
BOOL_OVERFLOW, NON_DIGIT_CHAR,
BOOL_INVALID_VALUE, INVALID_LEADING_CHAR,
NON_DIGIT_CHAR, POSITIVE_OVERFLOW,
INVALID_LEADING_CHAR, NEGATIVE_OVERFLOW,
POSITIVE_OVERFLOW, STRING_TO_FLOAT_ERROR,
NEGATIVE_OVERFLOW, NON_WHITESPACE_AFTER_END,
STRING_TO_FLOAT_ERROR, ARITH_POSITIVE_OVERFLOW,
NON_WHITESPACE_AFTER_END, ARITH_NEGATIVE_OVERFLOW,
ARITH_POSITIVE_OVERFLOW, ARITH_LOSS_OF_PRECISION,
ARITH_NEGATIVE_OVERFLOW, NUM_ERROR_CODES, // has to be the last entry
ARITH_LOSS_OF_PRECISION, };
NUM_ERROR_CODES, // has to be the last entry
};
ConversionError(const std::string& str, Code code) struct ConversionErrorBase : std::range_error {
: std::range_error(str), code_(code) {} using std::range_error::range_error;
};
ConversionError(const char* str, Code code) class ConversionError : public ConversionErrorBase {
: std::range_error(str), code_(code) {} public:
ConversionError(const std::string& str, ConversionCode code)
: ConversionErrorBase(str), code_(code) {}
Code errorCode() const { return code_; } ConversionError(const char* str, ConversionCode code)
: ConversionErrorBase(str), code_(code) {}
ConversionCode errorCode() const {
return code_;
}
private: private:
Code code_; ConversionCode code_;
}; };
namespace detail { /*******************************************************************************
* Custom Error Translation
ConversionError makeConversionError( *
ConversionError::Code code, * Your overloaded parseTo() function can return a custom error code on failure.
const char* input, * ::folly::to() will call makeConversionError to translate that error code into
size_t inputLen); * an object to throw. makeConversionError is found by argument-dependent
* lookup. It should have this signature:
inline ConversionError makeConversionError( *
ConversionError::Code code, * namespace other_namespace {
const std::string& str) { * enum YourErrorCode { BAD_ERROR, WORSE_ERROR };
return makeConversionError(code, str.data(), str.size()); *
} * struct YourConversionError : ConversionErrorBase {
* YourConversionError(const char* what) : ConversionErrorBase(what) {}
inline ConversionError makeConversionError( * };
ConversionError::Code code, *
StringPiece sp) { * YourConversionError
return makeConversionError(code, sp.data(), sp.size()); * makeConversionError(YourErrorCode code, ::folly::StringPiece sp) {
} * ...
* return YourConversionError(messageString);
* }
******************************************************************************/
ConversionError makeConversionError(ConversionCode code, StringPiece sp);
namespace detail {
/** /**
* Enforce that the suffix following a number is made up only of whitespace. * Enforce that the suffix following a number is made up only of whitespace.
*/ */
inline ConversionError::Code enforceWhitespaceErr(StringPiece sp) { inline ConversionCode enforceWhitespaceErr(StringPiece sp) {
for (auto c : sp) { for (auto c : sp) {
if (!std::isspace(c)) { if (UNLIKELY(!std::isspace(c))) {
return ConversionError::NON_WHITESPACE_AFTER_END; return ConversionCode::NON_WHITESPACE_AFTER_END;
} }
} }
return ConversionError::SUCCESS; return ConversionCode::SUCCESS;
} }
/** /**
...@@ -115,42 +128,57 @@ inline ConversionError::Code enforceWhitespaceErr(StringPiece sp) { ...@@ -115,42 +128,57 @@ inline ConversionError::Code enforceWhitespaceErr(StringPiece sp) {
*/ */
inline void enforceWhitespace(StringPiece sp) { inline void enforceWhitespace(StringPiece sp) {
auto err = enforceWhitespaceErr(sp); auto err = enforceWhitespaceErr(sp);
if (err != ConversionError::SUCCESS) { if (err != ConversionCode::SUCCESS) {
throw detail::makeConversionError(err, sp); throw makeConversionError(err, sp);
} }
} }
}
/** /**
* A simple std::pair-like wrapper to wrap both a value and an error * The identity conversion function.
* tryTo<T>(T) returns itself for all types T.
*/ */
template <typename T> template <class Tgt, class Src>
struct ConversionResult { typename std::enable_if<
explicit ConversionResult(T v) : value(v) {} std::is_same<Tgt, typename std::decay<Src>::type>::value,
explicit ConversionResult(ConversionError::Code e) : error(e) {} Expected<Tgt, ConversionCode>>::type
tryTo(Src&& value) {
bool success() const { return std::forward<Src>(value);
return error == ConversionError::SUCCESS; }
}
T value; template <class Tgt, class Src>
ConversionError::Code error{ConversionError::SUCCESS}; typename std::enable_if<
}; std::is_same<Tgt, typename std::decay<Src>::type>::value,
Tgt>::type
to(Src&& value) {
return std::forward<Src>(value);
} }
/*******************************************************************************
* Arithmetic to boolean
******************************************************************************/
/** /**
* The identity conversion function. * Unchecked conversion from arithmetic to boolean. This is different from the
* to<T>(T) returns itself for all types T. * other arithmetic conversions because we use the C convention of treating any
* non-zero value as true, instead of range checking.
*/ */
template <class Tgt, class Src> template <class Tgt, class Src>
typename std::enable_if<std::is_same<Tgt, Src>::value, Tgt>::type typename std::enable_if<
to(const Src & value) { std::is_arithmetic<Src>::value && !std::is_same<Tgt, Src>::value &&
return value; std::is_same<Tgt, bool>::value,
Expected<Tgt, ConversionCode>>::type
tryTo(const Src& value) {
return value != Src();
} }
template <class Tgt, class Src> template <class Tgt, class Src>
typename std::enable_if<std::is_same<Tgt, Src>::value, Tgt>::type typename std::enable_if<
to(Src && value) { std::is_arithmetic<Src>::value && !std::is_same<Tgt, Src>::value &&
return std::forward<Src>(value); std::is_same<Tgt, bool>::value,
Tgt>::type
to(const Src& value) {
return value != Src();
} }
/******************************************************************************* /*******************************************************************************
...@@ -159,34 +187,28 @@ to(Src && value) { ...@@ -159,34 +187,28 @@ to(Src && value) {
namespace detail { namespace detail {
template <class T> template <typename... Ts>
const T& getLastElement(const T & v) { struct LastElementImpl {
return v; static void call(Ignored<Ts>...) {}
} };
template <class T, class... Ts> template <typename Head, typename... Ts>
typename std::tuple_element< struct LastElementImpl<Head, Ts...> {
sizeof...(Ts), template <typename Last>
std::tuple<T, Ts...> >::type const& static Last call(Ignored<Ts>..., Last&& last) {
getLastElement(const T&, const Ts&... vs) { return std::forward<Last>(last);
return getLastElement(vs...); }
};
template <typename... Ts>
auto getLastElement(const Ts&... ts)
-> decltype(LastElementImpl<Ts...>::call(ts...)) {
return LastElementImpl<Ts...>::call(ts...);
} }
// This class exists to specialize away std::tuple_element in the case where we
// have 0 template arguments. Without this, Clang/libc++ will blow a
// static_assert even if tuple_element is protected by an enable_if.
template <class... Ts> template <class... Ts>
struct last_element { struct LastElement : std::decay<decltype(
typedef typename std::enable_if< LastElementImpl<Ts...>::call(std::declval<Ts>()...))> {
sizeof...(Ts) >= 1,
typename std::tuple_element<
sizeof...(Ts) - 1, std::tuple<Ts...>
>::type>::type type;
};
template <>
struct last_element<> {
typedef void type;
}; };
} // namespace detail } // namespace detail
...@@ -373,11 +395,10 @@ toAppend(Src value, Tgt * result) { ...@@ -373,11 +395,10 @@ toAppend(Src value, Tgt * result) {
} }
} }
template<class Src> template <class Src>
typename std::enable_if< typename std::enable_if<std::is_convertible<Src, const char*>::value, size_t>::
std::is_convertible<Src, const char*>::value, type
size_t>::type estimateSpaceNeeded(Src value) {
estimateSpaceNeeded(Src value) {
const char *c = value; const char *c = value;
if (c) { if (c) {
return folly::StringPiece(value).size(); return folly::StringPiece(value).size();
...@@ -747,11 +768,10 @@ toAppendStrImpl(const T& v, Tgt result) { ...@@ -747,11 +768,10 @@ toAppendStrImpl(const T& v, Tgt result) {
} }
template <class T, class... Ts> template <class T, class... Ts>
typename std::enable_if<sizeof...(Ts) >= 2 typename std::enable_if<
&& IsSomeString< sizeof...(Ts) >= 2 &&
typename std::remove_pointer< IsSomeString<typename std::remove_pointer<
typename detail::last_element<Ts...>::type typename detail::LastElement<const Ts&...>::type>::type>::value>::type
>::type>::value>::type
toAppendStrImpl(const T& v, const Ts&... vs) { toAppendStrImpl(const T& v, const Ts&... vs) {
toAppend(v, getLastElement(vs...)); toAppend(v, getLastElement(vs...));
toAppendStrImpl(vs...); toAppendStrImpl(vs...);
...@@ -765,11 +785,10 @@ toAppendDelimStrImpl(const Delimiter& /* delim */, const T& v, Tgt result) { ...@@ -765,11 +785,10 @@ toAppendDelimStrImpl(const Delimiter& /* delim */, const T& v, Tgt result) {
} }
template <class Delimiter, class T, class... Ts> template <class Delimiter, class T, class... Ts>
typename std::enable_if<sizeof...(Ts) >= 2 typename std::enable_if<
&& IsSomeString< sizeof...(Ts) >= 2 &&
typename std::remove_pointer< IsSomeString<typename std::remove_pointer<
typename detail::last_element<Ts...>::type typename detail::LastElement<const Ts&...>::type>::type>::value>::type
>::type>::value>::type
toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) { toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) {
// we are really careful here, calling toAppend with just one element does // we are really careful here, calling toAppend with just one element does
// not try to estimate space needed (as we already did that). If we call // not try to estimate space needed (as we already did that). If we call
...@@ -803,11 +822,10 @@ toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) { ...@@ -803,11 +822,10 @@ toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) {
* } * }
*/ */
template <class... Ts> template <class... Ts>
typename std::enable_if<sizeof...(Ts) >= 3 typename std::enable_if<
&& IsSomeString< sizeof...(Ts) >= 3 &&
typename std::remove_pointer< IsSomeString<typename std::remove_pointer<
typename detail::last_element<Ts...>::type typename detail::LastElement<const Ts&...>::type>::type>::value>::type
>::type>::value>::type
toAppend(const Ts&... vs) { toAppend(const Ts&... vs) {
::folly::detail::toAppendStrImpl(vs...); ::folly::detail::toAppendStrImpl(vs...);
} }
...@@ -833,11 +851,8 @@ void toAppend(const pid_t a, Tgt* res) { ...@@ -833,11 +851,8 @@ void toAppend(const pid_t a, Tgt* res) {
* will probably save a few calls to malloc. * will probably save a few calls to malloc.
*/ */
template <class... Ts> template <class... Ts>
typename std::enable_if< typename std::enable_if<IsSomeString<typename std::remove_pointer<
IsSomeString< typename detail::LastElement<const Ts&...>::type>::type>::value>::type
typename std::remove_pointer<
typename detail::last_element<Ts...>::type
>::type>::value>::type
toAppendFit(const Ts&... vs) { toAppendFit(const Ts&... vs) {
::folly::detail::reserveInTarget(vs...); ::folly::detail::reserveInTarget(vs...);
toAppend(vs...); toAppend(vs...);
...@@ -874,11 +889,10 @@ typename std::enable_if<IsSomeString<Tgt>::value>::type toAppendDelim( ...@@ -874,11 +889,10 @@ typename std::enable_if<IsSomeString<Tgt>::value>::type toAppendDelim(
* comments for toAppend for details about memory allocation. * comments for toAppend for details about memory allocation.
*/ */
template <class Delimiter, class... Ts> template <class Delimiter, class... Ts>
typename std::enable_if<sizeof...(Ts) >= 3 typename std::enable_if<
&& IsSomeString< sizeof...(Ts) >= 3 &&
typename std::remove_pointer< IsSomeString<typename std::remove_pointer<
typename detail::last_element<Ts...>::type typename detail::LastElement<const Ts&...>::type>::type>::value>::type
>::type>::value>::type
toAppendDelim(const Delimiter& delim, const Ts&... vs) { toAppendDelim(const Delimiter& delim, const Ts&... vs) {
detail::toAppendDelimStrImpl(delim, vs...); detail::toAppendDelimStrImpl(delim, vs...);
} }
...@@ -887,11 +901,8 @@ toAppendDelim(const Delimiter& delim, const Ts&... vs) { ...@@ -887,11 +901,8 @@ toAppendDelim(const Delimiter& delim, const Ts&... vs) {
* Detail in comment for toAppendFit * Detail in comment for toAppendFit
*/ */
template <class Delimiter, class... Ts> template <class Delimiter, class... Ts>
typename std::enable_if< typename std::enable_if<IsSomeString<typename std::remove_pointer<
IsSomeString< typename detail::LastElement<const Ts&...>::type>::type>::value>::type
typename std::remove_pointer<
typename detail::last_element<Ts...>::type
>::type>::value>::type
toAppendDelimFit(const Delimiter& delim, const Ts&... vs) { toAppendDelimFit(const Delimiter& delim, const Ts&... vs) {
detail::reserveInTargetDelim(delim, vs...); detail::reserveInTargetDelim(delim, vs...);
toAppendDelim(delim, vs...); toAppendDelim(delim, vs...);
...@@ -906,10 +917,11 @@ void toAppendDelimFit(const De&, const Ts&) {} ...@@ -906,10 +917,11 @@ void toAppendDelimFit(const De&, const Ts&) {}
*/ */
template <class Tgt, class... Ts> template <class Tgt, class... Ts>
typename std::enable_if< typename std::enable_if<
IsSomeString<Tgt>::value && ( IsSomeString<Tgt>::value &&
sizeof...(Ts) != 1 || (sizeof...(Ts) != 1 ||
!std::is_same<Tgt, typename detail::last_element<Ts...>::type>::value), !std::is_same<Tgt, typename detail::LastElement<const Ts&...>::type>::
Tgt>::type value),
Tgt>::type
to(const Ts&... vs) { to(const Ts&... vs) {
Tgt result; Tgt result;
toAppendFit(vs..., &result); toAppendFit(vs..., &result);
...@@ -933,10 +945,11 @@ toDelim(const Delim& /* delim */, const Src& value) { ...@@ -933,10 +945,11 @@ toDelim(const Delim& /* delim */, const Src& value) {
*/ */
template <class Tgt, class Delim, class... Ts> template <class Tgt, class Delim, class... Ts>
typename std::enable_if< typename std::enable_if<
IsSomeString<Tgt>::value && ( IsSomeString<Tgt>::value &&
sizeof...(Ts) != 1 || (sizeof...(Ts) != 1 ||
!std::is_same<Tgt, typename detail::last_element<Ts...>::type>::value), !std::is_same<Tgt, typename detail::LastElement<const Ts&...>::type>::
Tgt>::type value),
Tgt>::type
toDelim(const Delim& delim, const Ts&... vs) { toDelim(const Delim& delim, const Ts&... vs) {
Tgt result; Tgt result;
toAppendDelimFit(delim, vs..., &result); toAppendDelimFit(delim, vs..., &result);
...@@ -949,130 +962,121 @@ toDelim(const Delim& delim, const Ts&... vs) { ...@@ -949,130 +962,121 @@ toDelim(const Delim& delim, const Ts&... vs) {
namespace detail { namespace detail {
ConversionResult<bool> str_to_bool(StringPiece* src); Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept;
template <typename T> template <typename T>
ConversionResult<T> str_to_floating(StringPiece* src); Expected<T, ConversionCode> str_to_floating(StringPiece* src) noexcept;
extern template ConversionResult<float> str_to_floating<float>( extern template Expected<float, ConversionCode> str_to_floating<float>(
StringPiece* src); StringPiece* src) noexcept;
extern template ConversionResult<double> str_to_floating<double>( extern template Expected<double, ConversionCode> str_to_floating<double>(
StringPiece* src); StringPiece* src) noexcept;
template <class Tgt> template <class Tgt>
ConversionResult<Tgt> digits_to(const char* b, const char* e); Expected<Tgt, ConversionCode> digits_to(const char* b, const char* e) noexcept;
extern template ConversionResult<char> digits_to<char>( extern template Expected<char, ConversionCode> digits_to<char>(
const char*,
const char*);
extern template ConversionResult<signed char> digits_to<signed char>(
const char*, const char*,
const char*); const char*) noexcept;
extern template ConversionResult<unsigned char> digits_to<unsigned char>( extern template Expected<signed char, ConversionCode> digits_to<signed char>(
const char*, const char*,
const char*); const char*) noexcept;
extern template Expected<unsigned char, ConversionCode>
digits_to<unsigned char>(const char*, const char*) noexcept;
extern template ConversionResult<short> digits_to<short>( extern template Expected<short, ConversionCode> digits_to<short>(
const char*, const char*,
const char*); const char*) noexcept;
extern template ConversionResult<unsigned short> digits_to<unsigned short>( extern template Expected<unsigned short, ConversionCode>
const char*, digits_to<unsigned short>(const char*, const char*) noexcept;
const char*);
extern template ConversionResult<int> digits_to<int>(const char*, const char*); extern template Expected<int, ConversionCode> digits_to<int>(
extern template ConversionResult<unsigned int> digits_to<unsigned int>(
const char*, const char*,
const char*); const char*) noexcept;
extern template Expected<unsigned int, ConversionCode> digits_to<unsigned int>(
extern template ConversionResult<long> digits_to<long>(
const char*, const char*,
const char*); const char*) noexcept;
extern template ConversionResult<unsigned long> digits_to<unsigned long>(
extern template Expected<long, ConversionCode> digits_to<long>(
const char*, const char*,
const char*); const char*) noexcept;
extern template Expected<unsigned long, ConversionCode>
digits_to<unsigned long>(const char*, const char*) noexcept;
extern template ConversionResult<long long> digits_to<long long>( extern template Expected<long long, ConversionCode> digits_to<long long>(
const char*, const char*,
const char*); const char*) noexcept;
extern template ConversionResult<unsigned long long> extern template Expected<unsigned long long, ConversionCode>
digits_to<unsigned long long>(const char*, const char*); digits_to<unsigned long long>(const char*, const char*) noexcept;
#if FOLLY_HAVE_INT128_T #if FOLLY_HAVE_INT128_T
extern template ConversionResult<__int128> digits_to<__int128>( extern template Expected<__int128, ConversionCode> digits_to<__int128>(
const char*, const char*,
const char*); const char*) noexcept;
extern template ConversionResult<unsigned __int128> extern template Expected<unsigned __int128, ConversionCode>
digits_to<unsigned __int128>(const char*, const char*); digits_to<unsigned __int128>(const char*, const char*) noexcept;
#endif #endif
template <class T> template <class T>
ConversionResult<T> str_to_integral(StringPiece* src); Expected<T, ConversionCode> str_to_integral(StringPiece* src) noexcept;
extern template ConversionResult<char> str_to_integral<char>(StringPiece* src); extern template Expected<char, ConversionCode> str_to_integral<char>(
extern template ConversionResult<signed char> str_to_integral<signed char>( StringPiece* src) noexcept;
StringPiece* src); extern template Expected<signed char, ConversionCode>
extern template ConversionResult<unsigned char> str_to_integral<unsigned char>( str_to_integral<signed char>(StringPiece* src) noexcept;
StringPiece* src); extern template Expected<unsigned char, ConversionCode>
str_to_integral<unsigned char>(StringPiece* src) noexcept;
extern template ConversionResult<short> str_to_integral<short>(
StringPiece* src); extern template Expected<short, ConversionCode> str_to_integral<short>(
extern template ConversionResult<unsigned short> StringPiece* src) noexcept;
str_to_integral<unsigned short>(StringPiece* src); extern template Expected<unsigned short, ConversionCode>
str_to_integral<unsigned short>(StringPiece* src) noexcept;
extern template ConversionResult<int> str_to_integral<int>(StringPiece* src);
extern template ConversionResult<unsigned int> str_to_integral<unsigned int>( extern template Expected<int, ConversionCode> str_to_integral<int>(
StringPiece* src); StringPiece* src) noexcept;
extern template Expected<unsigned int, ConversionCode>
extern template ConversionResult<long> str_to_integral<long>(StringPiece* src); str_to_integral<unsigned int>(StringPiece* src) noexcept;
extern template ConversionResult<unsigned long> str_to_integral<unsigned long>(
StringPiece* src); extern template Expected<long, ConversionCode> str_to_integral<long>(
StringPiece* src) noexcept;
extern template ConversionResult<long long> str_to_integral<long long>( extern template Expected<unsigned long, ConversionCode>
StringPiece* src); str_to_integral<unsigned long>(StringPiece* src) noexcept;
extern template ConversionResult<unsigned long long>
str_to_integral<unsigned long long>(StringPiece* src); extern template Expected<long long, ConversionCode> str_to_integral<long long>(
StringPiece* src) noexcept;
extern template Expected<unsigned long long, ConversionCode>
str_to_integral<unsigned long long>(StringPiece* src) noexcept;
#if FOLLY_HAVE_INT128_T #if FOLLY_HAVE_INT128_T
extern template ConversionResult<__int128> str_to_integral<__int128>( extern template Expected<__int128, ConversionCode> str_to_integral<__int128>(
StringPiece* src); StringPiece* src) noexcept;
extern template ConversionResult<unsigned __int128> extern template Expected<unsigned __int128, ConversionCode>
str_to_integral<unsigned __int128>(StringPiece* src); str_to_integral<unsigned __int128>(StringPiece* src) noexcept;
#endif #endif
template <typename T> template <typename T>
typename std::enable_if<std::is_same<T, bool>::value, ConversionResult<T>>::type typename std::
convertTo(StringPiece* src) { enable_if<std::is_same<T, bool>::value, Expected<T, ConversionCode>>::type
convertTo(StringPiece* src) noexcept {
return str_to_bool(src); return str_to_bool(src);
} }
template <typename T> template <typename T>
typename std::enable_if< typename std::enable_if<
std::is_floating_point<T>::value, ConversionResult<T>>::type std::is_floating_point<T>::value,
convertTo(StringPiece* src) { Expected<T, ConversionCode>>::type
convertTo(StringPiece* src) noexcept {
return str_to_floating<T>(src); return str_to_floating<T>(src);
} }
template <typename T> template <typename T>
typename std::enable_if< typename std::enable_if<
std::is_integral<T>::value && !std::is_same<T, bool>::value, std::is_integral<T>::value && !std::is_same<T, bool>::value,
ConversionResult<T>>::type Expected<T, ConversionCode>>::type
convertTo(StringPiece* src) { convertTo(StringPiece* src) noexcept {
return str_to_integral<T>(src); return str_to_integral<T>(src);
} }
template <typename T>
struct WrapperInfo { using type = T; };
template <typename T, typename Gen>
typename std::enable_if<
std::is_same<typename WrapperInfo<T>::type, T>::value, T>::type
inline wrap(ConversionResult<typename WrapperInfo<T>::type> res, Gen&& gen) {
if (LIKELY(res.success())) {
return res.value;
}
throw detail::makeConversionError(res.error, gen());
}
} // namespace detail } // namespace detail
/** /**
...@@ -1081,12 +1085,22 @@ inline wrap(ConversionResult<typename WrapperInfo<T>::type> res, Gen&& gen) { ...@@ -1081,12 +1085,22 @@ inline wrap(ConversionResult<typename WrapperInfo<T>::type> res, Gen&& gen) {
*/ */
template <typename Tgt> template <typename Tgt>
typename std::enable_if< typename std::enable_if<
std::is_integral<typename detail::WrapperInfo<Tgt>::type>::value && std::is_integral<Tgt>::value && !std::is_same<Tgt, bool>::value,
!std::is_same<typename detail::WrapperInfo<Tgt>::type, bool>::value, Expected<Tgt, ConversionCode>>::type
tryTo(const char* b, const char* e) {
return detail::digits_to<Tgt>(b, e);
}
template <typename Tgt>
typename std::enable_if<
std::is_integral<Tgt>::value && !std::is_same<Tgt, bool>::value,
Tgt>::type Tgt>::type
to(const char* b, const char* e) { to(const char* b, const char* e) {
auto res = detail::digits_to<typename detail::WrapperInfo<Tgt>::type>(b, e); return tryTo<Tgt>(b, e).thenOrThrow(
return detail::wrap<Tgt>(res, [&] { return StringPiece(b, e); }); [](Tgt res) { return res; },
[=](ConversionCode code) {
return makeConversionError(code, StringPiece(b, e));
});
} }
/******************************************************************************* /*******************************************************************************
...@@ -1094,47 +1108,21 @@ to(const char* b, const char* e) { ...@@ -1094,47 +1108,21 @@ to(const char* b, const char* e) {
******************************************************************************/ ******************************************************************************/
/** /**
* Parsing strings to numeric types. These routines differ from * Parsing strings to numeric types.
* parseTo(str, numeric) routines in that they take a POINTER TO a StringPiece
* and alter that StringPiece to reflect progress information.
*/ */
template <typename Tgt> template <typename Tgt>
typename std::enable_if< FOLLY_WARN_UNUSED_RESULT inline typename std::enable_if<
std::is_arithmetic<typename detail::WrapperInfo<Tgt>::type>::value>::type std::is_arithmetic<Tgt>::value,
parseTo(StringPiece* src, Tgt& out) { Expected<StringPiece, ConversionCode>>::type
auto res = detail::convertTo<typename detail::WrapperInfo<Tgt>::type>(src);
out = detail::wrap<Tgt>(res, [&] { return *src; });
}
template <typename Tgt>
typename std::enable_if<
std::is_arithmetic<typename detail::WrapperInfo<Tgt>::type>::value>::type
parseTo(StringPiece src, Tgt& out) { parseTo(StringPiece src, Tgt& out) {
auto res = detail::convertTo<typename detail::WrapperInfo<Tgt>::type>(&src); return detail::convertTo<Tgt>(&src).then(
if (LIKELY(res.success())) { [&](Tgt res) { return void(out = res), src; });
res.error = detail::enforceWhitespaceErr(src);
}
out = detail::wrap<Tgt>(res, [&] { return src; });
} }
/******************************************************************************* /*******************************************************************************
* Integral / Floating Point to integral / Floating Point * Integral / Floating Point to integral / Floating Point
******************************************************************************/ ******************************************************************************/
/**
* Unchecked conversion from arithmetic to boolean. This is different from the
* other arithmetic conversions because we use the C convention of treating any
* non-zero value as true, instead of range checking.
*/
template <class Tgt, class Src>
typename std::enable_if<
std::is_arithmetic<Src>::value && !std::is_same<Tgt, Src>::value &&
std::is_same<Tgt, bool>::value,
Tgt>::type
to(const Src& value) {
return value != Src();
}
namespace detail { namespace detail {
/** /**
...@@ -1147,22 +1135,22 @@ typename std::enable_if< ...@@ -1147,22 +1135,22 @@ typename std::enable_if<
std::is_integral<Src>::value && !std::is_same<Tgt, Src>::value && std::is_integral<Src>::value && !std::is_same<Tgt, Src>::value &&
!std::is_same<Tgt, bool>::value && !std::is_same<Tgt, bool>::value &&
std::is_integral<Tgt>::value, std::is_integral<Tgt>::value,
ConversionResult<Tgt>>::type Expected<Tgt, ConversionCode>>::type
convertTo(const Src& value) { convertTo(const Src& value) noexcept {
/* static */ if ( /* static */ if (
std::numeric_limits<Tgt>::max() < std::numeric_limits<Src>::max()) { std::numeric_limits<Tgt>::max() < std::numeric_limits<Src>::max()) {
if (greater_than<Tgt, std::numeric_limits<Tgt>::max()>(value)) { if (greater_than<Tgt, std::numeric_limits<Tgt>::max()>(value)) {
return ConversionResult<Tgt>(ConversionError::ARITH_POSITIVE_OVERFLOW); return makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW);
} }
} }
/* static */ if ( /* static */ if (
std::is_signed<Src>::value && std::is_signed<Src>::value &&
(!std::is_signed<Tgt>::value || sizeof(Src) > sizeof(Tgt))) { (!std::is_signed<Tgt>::value || sizeof(Src) > sizeof(Tgt))) {
if (less_than<Tgt, std::numeric_limits<Tgt>::min()>(value)) { if (less_than<Tgt, std::numeric_limits<Tgt>::min()>(value)) {
return ConversionResult<Tgt>(ConversionError::ARITH_NEGATIVE_OVERFLOW); return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW);
} }
} }
return ConversionResult<Tgt>(static_cast<Tgt>(value)); return static_cast<Tgt>(value);
} }
/** /**
...@@ -1174,18 +1162,18 @@ template <class Tgt, class Src> ...@@ -1174,18 +1162,18 @@ template <class Tgt, class Src>
typename std::enable_if< typename std::enable_if<
std::is_floating_point<Tgt>::value && std::is_floating_point<Src>::value && std::is_floating_point<Tgt>::value && std::is_floating_point<Src>::value &&
!std::is_same<Tgt, Src>::value, !std::is_same<Tgt, Src>::value,
ConversionResult<Tgt>>::type Expected<Tgt, ConversionCode>>::type
convertTo(const Src& value) { convertTo(const Src& value) noexcept {
/* static */ if ( /* static */ if (
std::numeric_limits<Tgt>::max() < std::numeric_limits<Src>::max()) { std::numeric_limits<Tgt>::max() < std::numeric_limits<Src>::max()) {
if (value > std::numeric_limits<Tgt>::max()) { if (value > std::numeric_limits<Tgt>::max()) {
return ConversionResult<Tgt>(ConversionError::ARITH_POSITIVE_OVERFLOW); return makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW);
} }
if (value < std::numeric_limits<Tgt>::lowest()) { if (value < std::numeric_limits<Tgt>::lowest()) {
return ConversionResult<Tgt>(ConversionError::ARITH_NEGATIVE_OVERFLOW); return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW);
} }
} }
return ConversionResult<Tgt>(boost::implicit_cast<Tgt>(value)); return boost::implicit_cast<Tgt>(value);
} }
/** /**
...@@ -1252,18 +1240,18 @@ template <typename Tgt, typename Src> ...@@ -1252,18 +1240,18 @@ template <typename Tgt, typename Src>
typename std::enable_if< typename std::enable_if<
(std::is_integral<Src>::value && std::is_floating_point<Tgt>::value) || (std::is_integral<Src>::value && std::is_floating_point<Tgt>::value) ||
(std::is_floating_point<Src>::value && std::is_integral<Tgt>::value), (std::is_floating_point<Src>::value && std::is_integral<Tgt>::value),
ConversionResult<Tgt>>::type Expected<Tgt, ConversionCode>>::type
convertTo(const Src& value) { convertTo(const Src& value) noexcept {
if (LIKELY(checkConversion<Tgt>(value))) { if (LIKELY(checkConversion<Tgt>(value))) {
Tgt result = static_cast<Tgt>(value); Tgt result = static_cast<Tgt>(value);
if (LIKELY(checkConversion<Src>(result))) { if (LIKELY(checkConversion<Src>(result))) {
Src witness = static_cast<Src>(result); Src witness = static_cast<Src>(result);
if (LIKELY(value == witness)) { if (LIKELY(value == witness)) {
return ConversionResult<Tgt>(result); return result;
} }
} }
} }
return ConversionResult<Tgt>(ConversionError::ARITH_LOSS_OF_PRECISION); return makeUnexpected(ConversionCode::ARITH_LOSS_OF_PRECISION);
} }
template <typename Tgt, typename Src> template <typename Tgt, typename Src>
...@@ -1286,13 +1274,20 @@ using IsArithToArith = std::integral_constant< ...@@ -1286,13 +1274,20 @@ using IsArithToArith = std::integral_constant<
template <typename Tgt, typename Src> template <typename Tgt, typename Src>
typename std::enable_if< typename std::enable_if<
detail::IsArithToArith< detail::IsArithToArith<Tgt, Src>::value,
typename detail::WrapperInfo<Tgt>::type, Src>::value, Tgt>::type Expected<Tgt, ConversionCode>>::type
to(const Src& value) { tryTo(const Src& value) noexcept {
auto res = detail::convertTo<typename detail::WrapperInfo<Tgt>::type>(value); return detail::convertTo<Tgt>(value);
return detail::wrap<Tgt>(res, [&] { }
return detail::errorValue<typename detail::WrapperInfo<Tgt>::type>(value);
}); template <typename Tgt, typename Src>
typename std::enable_if<detail::IsArithToArith<Tgt, Src>::value, Tgt>::type to(
const Src& value) {
return tryTo<Tgt>(value).thenOrThrow(
[](Tgt res) { return res; },
[&](ConversionCode e) {
return makeConversionError(e, detail::errorValue<Tgt>(value));
});
} }
/******************************************************************************* /*******************************************************************************
...@@ -1303,49 +1298,157 @@ to(const Src& value) { ...@@ -1303,49 +1298,157 @@ to(const Src& value) {
* argument-dependent lookup: * argument-dependent lookup:
* *
* namespace other_namespace { * namespace other_namespace {
* void parseTo(::folly::StringPiece, OtherType&); * ::folly::Expected<::folly::StringPiece, SomeErrorCode>
* parseTo(::folly::StringPiece, OtherType&) noexcept;
* } * }
******************************************************************************/ ******************************************************************************/
template <class T> template <class T>
typename std::enable_if<std::is_enum<T>::value>::type FOLLY_WARN_UNUSED_RESULT typename std::enable_if<
parseTo(StringPiece in, T& out) { std::is_enum<T>::value,
typename std::underlying_type<T>::type tmp; Expected<StringPiece, ConversionCode>>::type
parseTo(in, tmp); parseTo(StringPiece in, T& out) noexcept {
out = static_cast<T>(tmp); typename std::underlying_type<T>::type tmp{};
} auto restOrError = parseTo(in, tmp);
out = static_cast<T>(tmp); // Harmless if parseTo fails
inline void parseTo(StringPiece in, StringPiece& out) { return restOrError;
}
FOLLY_WARN_UNUSED_RESULT
inline Expected<StringPiece, ConversionCode> parseTo(
StringPiece in,
StringPiece& out) noexcept {
out = in; out = in;
return StringPiece{in.end(), in.end()};
} }
inline void parseTo(StringPiece in, std::string& out) { FOLLY_WARN_UNUSED_RESULT
inline Expected<StringPiece, ConversionCode> parseTo(
StringPiece in,
std::string& out) {
out.clear(); out.clear();
out.append(in.data(), in.size()); out.append(in.data(), in.size()); // TODO try/catch?
return StringPiece{in.end(), in.end()};
} }
inline void parseTo(StringPiece in, fbstring& out) { FOLLY_WARN_UNUSED_RESULT
inline Expected<StringPiece, ConversionCode> parseTo(
StringPiece in,
fbstring& out) {
out.clear(); out.clear();
out.append(in.data(), in.size()); out.append(in.data(), in.size()); // TODO try/catch?
return StringPiece{in.end(), in.end()};
} }
namespace detail {
template <typename Tgt>
using ParseToResult = decltype(parseTo(StringPiece{}, std::declval<Tgt&>()));
struct CheckTrailingSpace {
Expected<Unit, ConversionCode> operator()(StringPiece sp) const {
auto e = enforceWhitespaceErr(sp);
if (UNLIKELY(e != ConversionCode::SUCCESS))
return makeUnexpected(e);
return unit;
}
};
template <class Error>
struct ReturnUnit {
template <class T>
constexpr Expected<Unit, Error> operator()(T&&) const {
return unit;
}
};
// Older versions of the parseTo customization point threw on error and
// returned void. Handle that.
template <class Tgt>
inline typename std::enable_if<
std::is_void<ParseToResult<Tgt>>::value,
Expected<StringPiece, ConversionCode>>::type
parseToWrap(StringPiece sp, Tgt& out) {
parseTo(sp, out);
return StringPiece(sp.end(), sp.end());
}
template <class Tgt>
inline typename std::enable_if<
!std::is_void<ParseToResult<Tgt>>::value,
ParseToResult<Tgt>>::type
parseToWrap(StringPiece sp, Tgt& out) {
return parseTo(sp, out);
}
template <typename Tgt>
using ParseToError = ExpectedErrorType<decltype(
detail::parseToWrap(StringPiece{}, std::declval<Tgt&>()))>;
} // namespace detail
/** /**
* String or StringPiece to target conversion. Accepts leading and trailing * String or StringPiece to target conversion. Accepts leading and trailing
* whitespace, but no non-space trailing characters. * whitespace, but no non-space trailing characters.
*/ */
template <class Tgt> template <class Tgt>
typename std::enable_if<!std::is_same<StringPiece, Tgt>::value, Tgt>::type inline typename std::enable_if<
to(StringPiece src) { !std::is_same<StringPiece, Tgt>::value,
Expected<Tgt, detail::ParseToError<Tgt>>>::type
tryTo(StringPiece src) {
Tgt result{};
using Error = detail::ParseToError<Tgt>;
using Check = typename std::conditional<
std::is_arithmetic<Tgt>::value,
detail::CheckTrailingSpace,
detail::ReturnUnit<Error>>::type;
return parseTo(src, result).then(Check(), [&](Unit) {
return std::move(result);
});
}
template <class Tgt>
inline
typename std::enable_if<!std::is_same<StringPiece, Tgt>::value, Tgt>::type
to(StringPiece src) {
Tgt result{};
using Error = detail::ParseToError<Tgt>;
using Check = typename std::conditional<
std::is_arithmetic<Tgt>::value,
detail::CheckTrailingSpace,
detail::ReturnUnit<Error>>::type;
auto tmp = detail::parseToWrap(src, result);
return tmp
.thenOrThrow(Check(), [&](Error e) { throw makeConversionError(e, src); })
.thenOrThrow(
[&](Unit) { return std::move(result); },
[&](Error e) { throw makeConversionError(e, tmp.value()); });
}
/**
* tryTo/to that take the strings by pointer so the caller gets information
* about how much of the string was consumed by the conversion. These do not
* check for trailing whitepsace.
*/
template <class Tgt>
Expected<Tgt, detail::ParseToError<Tgt>> tryTo(StringPiece* src) {
Tgt result; Tgt result;
parseTo(src, result); return parseTo(*src, result).then([&, src](StringPiece sp) -> Tgt {
return result; *src = sp;
return std::move(result);
});
} }
template <class Tgt> template <class Tgt>
Tgt to(StringPiece* src) { Tgt to(StringPiece* src) {
Tgt result; Tgt result;
parseTo(src, result); using Error = detail::ParseToError<Tgt>;
return result; return parseTo(*src, result)
.thenOrThrow(
[&, src](StringPiece sp) -> Tgt {
*src = sp;
return std::move(result);
},
[=](Error e) { return makeConversionError(e, *src); });
} }
/******************************************************************************* /*******************************************************************************
...@@ -1354,8 +1457,27 @@ Tgt to(StringPiece* src) { ...@@ -1354,8 +1457,27 @@ Tgt to(StringPiece* src) {
template <class Tgt, class Src> template <class Tgt, class Src>
typename std::enable_if< typename std::enable_if<
std::is_enum<Src>::value && !std::is_same<Src, Tgt>::value, Tgt>::type std::is_enum<Src>::value && !std::is_same<Src, Tgt>::value,
to(const Src & value) { Expected<Tgt, ConversionCode>>::type
tryTo(const Src& value) {
using I = typename std::underlying_type<Src>::type;
return tryTo<Tgt>(static_cast<I>(value));
}
template <class Tgt, class Src>
typename std::enable_if<
std::is_enum<Tgt>::value && !std::is_same<Src, Tgt>::value,
Tgt>::type
tryTo(const Src& value) {
using I = typename std::underlying_type<Tgt>::type;
return tryTo<I>(value).then([](I i) { return static_cast<Tgt>(i); });
}
template <class Tgt, class Src>
typename std::enable_if<
std::is_enum<Src>::value && !std::is_same<Src, Tgt>::value,
Tgt>::type
to(const Src& value) {
return to<Tgt>(static_cast<typename std::underlying_type<Src>::type>(value)); return to<Tgt>(static_cast<typename std::underlying_type<Src>::type>(value));
} }
......
...@@ -92,6 +92,15 @@ constexpr bool kHasUnalignedAccess = false; ...@@ -92,6 +92,15 @@ constexpr bool kHasUnalignedAccess = false;
# define FOLLY_ALWAYS_INLINE inline # define FOLLY_ALWAYS_INLINE inline
#endif #endif
// warn unused result
#if defined(_MSC_VER) && (_MSC_VER >= 1700)
#define FOLLY_WARN_UNUSED_RESULT _Check_return_
#elif defined(__clang__) || defined(__GNUC__)
#define FOLLY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
#else
#define FOLLY_WARN_UNUSED_RESULT
#endif
// target // target
#ifdef _MSC_VER #ifdef _MSC_VER
# define FOLLY_TARGET_ATTRIBUTE(target) # define FOLLY_TARGET_ATTRIBUTE(target)
......
...@@ -328,7 +328,7 @@ bool splitFixed(const Delim& delimiter, StringPiece input, OutputType& output) { ...@@ -328,7 +328,7 @@ bool splitFixed(const Delim& delimiter, StringPiece input, OutputType& output) {
if (exact && UNLIKELY(std::string::npos != input.find(delimiter))) { if (exact && UNLIKELY(std::string::npos != input.find(delimiter))) {
return false; return false;
} }
parseTo(input, output); output = folly::to<OutputType>(input);
return true; return true;
} }
...@@ -346,7 +346,7 @@ bool splitFixed( ...@@ -346,7 +346,7 @@ bool splitFixed(
StringPiece tail(input.begin() + cut + detail::delimSize(delimiter), StringPiece tail(input.begin() + cut + detail::delimSize(delimiter),
input.end()); input.end());
if (LIKELY(splitFixed<exact>(delimiter, tail, outTail...))) { if (LIKELY(splitFixed<exact>(delimiter, tail, outTail...))) {
parseTo(head, outHead); outHead = folly::to<OutputType>(head);
return true; return true;
} }
return false; return false;
......
...@@ -491,7 +491,8 @@ struct IsConvertible { ...@@ -491,7 +491,8 @@ struct IsConvertible {
template <class T> template <class T>
struct IsConvertible< struct IsConvertible<
T, T,
decltype(parseTo(std::declval<folly::StringPiece>(), std::declval<T&>()))> { decltype(static_cast<void>(
parseTo(std::declval<folly::StringPiece>(), std::declval<T&>())))> {
enum { value = true }; enum { value = true };
}; };
......
...@@ -215,3 +215,31 @@ is returned, which can be tested for as follows: ...@@ -215,3 +215,31 @@ is returned, which can be tested for as follows:
// string could not be parsed // string could not be parsed
} }
``` ```
#### Non-throwing interfaces
`tryTo<T>` is the non-throwing variant of `to<T>`. It returns
an `Expected<T, ConversionCode>`. You can think of `Expected`
as like an `Optional<T>`, but if the conversion failed, `Expected`
stores an error code instead of a `T`.
`tryTo<T>` has similar performance as `to<T>` when the
conversion is successful. On the error path, you can expect
`tryTo<T>` to be roughly three orders of magnitude faster than
the throwing `to<T>` and to completely avoid any lock contention
arising from stack unwinding.
Here is how to use non-throwing conversions:
``` Cpp
auto t1 = tryTo<int>(str);
if (t1.hasValue()) {
use(t1.value());
}
```
`Expected` has a composability feature to make the above pattern simpler.
``` Cpp
tryTo<int>(str).then([](int i) { use(i); });
```
...@@ -911,6 +911,16 @@ inline void stringToTypeClassic(const char* str, uint32_t n) { ...@@ -911,6 +911,16 @@ inline void stringToTypeClassic(const char* str, uint32_t n) {
} }
} }
template <typename T>
inline void stringToTypeOptional(const char* str, uint32_t n) {
for (uint32_t i = 0; i < n; ++i) {
auto val = tryTo<T>(str);
if (val.hasValue()) {
doNotOptimizeAway(val.value());
}
}
}
template <typename T> template <typename T>
inline void ptrPairToIntClassic(StringPiece sp, uint32_t n) { inline void ptrPairToIntClassic(StringPiece sp, uint32_t n) {
for (uint32_t i = 0; i < n; ++i) { for (uint32_t i = 0; i < n; ++i) {
...@@ -924,6 +934,16 @@ inline void ptrPairToIntClassic(StringPiece sp, uint32_t n) { ...@@ -924,6 +934,16 @@ inline void ptrPairToIntClassic(StringPiece sp, uint32_t n) {
} }
} }
template <typename T>
inline void ptrPairToIntOptional(StringPiece sp, uint32_t n) {
for (uint32_t i = 0; i < n; ++i) {
auto val = tryTo<T>(sp.begin(), sp.end());
if (val.hasValue()) {
doNotOptimizeAway(val.value());
}
}
}
constexpr uint32_t kArithNumIter = 10000; constexpr uint32_t kArithNumIter = 10000;
template <typename T, typename U> template <typename T, typename U>
...@@ -944,6 +964,24 @@ inline size_t arithToArithClassic(const U* in, uint32_t numItems) { ...@@ -944,6 +964,24 @@ inline size_t arithToArithClassic(const U* in, uint32_t numItems) {
return kArithNumIter * numItems; return kArithNumIter * numItems;
} }
template <typename T, typename U>
inline size_t arithToArithOptional(const U* in, uint32_t numItems) {
for (uint32_t i = 0; i < kArithNumIter; ++i) {
for (uint32_t j = 0; j < numItems; ++j) {
auto val = tryTo<T>(*in);
doNotOptimizeAway(val.hasValue());
if (val.hasValue()) {
auto v2 = val.value();
doNotOptimizeAway(v2);
}
doNotOptimizeAway(j);
}
doNotOptimizeAway(i);
}
return kArithNumIter * numItems;
}
} // namespace } // namespace
namespace folly { namespace folly {
...@@ -980,6 +1018,12 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}}; ...@@ -980,6 +1018,12 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}};
} \ } \
BENCHMARK(stringTo##name##ClassicError, n) { \ BENCHMARK(stringTo##name##ClassicError, n) { \
stringToTypeClassic<type>(fail, n); \ stringToTypeClassic<type>(fail, n); \
} \
BENCHMARK(stringTo##name##Optional, n) { \
stringToTypeOptional<type>(pass, n); \
} \
BENCHMARK(stringTo##name##OptionalError, n) { \
stringToTypeOptional<type>(fail, n); \
} }
#define PTR_PAIR_TO_INT_BENCHMARK(type, name, pass, fail) \ #define PTR_PAIR_TO_INT_BENCHMARK(type, name, pass, fail) \
...@@ -988,14 +1032,26 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}}; ...@@ -988,14 +1032,26 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}};
} \ } \
BENCHMARK(ptrPairTo##name##ClassicError, n) { \ BENCHMARK(ptrPairTo##name##ClassicError, n) { \
ptrPairToIntClassic<type>(fail, n); \ ptrPairToIntClassic<type>(fail, n); \
} \
BENCHMARK(ptrPairTo##name##Optional, n) { \
ptrPairToIntOptional<type>(pass, n); \
} \
BENCHMARK(ptrPairTo##name##OptionalError, n) { \
ptrPairToIntOptional<type>(fail, n); \
} }
#define ARITH_TO_ARITH_BENCHMARK(type, name, pass, fail) \ #define ARITH_TO_ARITH_BENCHMARK(type, name, pass, fail) \
BENCHMARK_MULTI(name##Classic) { \ BENCHMARK_MULTI(name##Classic) { \
return arithToArithClassic<type>(pass.data(), pass.size()); \ return arithToArithClassic<type>(pass.data(), pass.size()); \
} \ } \
BENCHMARK_MULTI(name##ClassicError) { \ BENCHMARK_MULTI(name##ClassicError) { \
return arithToArithClassic<type>(fail.data(), fail.size()); \ return arithToArithClassic<type>(fail.data(), fail.size()); \
} \
BENCHMARK_MULTI(name##Optional) { \
return arithToArithOptional<type>(pass.data(), pass.size()); \
} \
BENCHMARK_MULTI(name##OptionalError) { \
return arithToArithOptional<type>(fail.data(), fail.size()); \
} }
#define INT_TO_ARITH_BENCHMARK(type, name, pass, fail) \ #define INT_TO_ARITH_BENCHMARK(type, name, pass, fail) \
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <folly/Conv.h> #include <folly/Conv.h>
#include <folly/Foreach.h> #include <folly/Foreach.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <algorithm>
#include <limits> #include <limits>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
...@@ -820,6 +821,14 @@ TEST(Conv, StringToBool) { ...@@ -820,6 +821,14 @@ TEST(Conv, StringToBool) {
EXPECT_EQ(buf5, sp5.begin()); EXPECT_EQ(buf5, sp5.begin());
} }
TEST(Conv, Transform) {
const std::vector<int64_t> in{1, 2, 3};
std::vector<std::string> out(in.size());
std::transform(in.begin(), in.end(), out.begin(), to<std::string, int64_t>);
const std::vector<std::string> ref{"1", "2", "3"};
EXPECT_EQ(ref, out);
}
TEST(Conv, FloatToInt) { TEST(Conv, FloatToInt) {
EXPECT_EQ(to<int>(42.0f), 42); EXPECT_EQ(to<int>(42.0f), 42);
EXPECT_EQ(to<int8_t>(-128.0f), int8_t(-128)); EXPECT_EQ(to<int8_t>(-128.0f), int8_t(-128));
...@@ -874,7 +883,7 @@ template <typename F> ...@@ -874,7 +883,7 @@ template <typename F>
void testConvError( void testConvError(
F&& expr, F&& expr,
const char* exprStr, const char* exprStr,
ConversionError::Code code, ConversionCode code,
const char* value, const char* value,
bool quotedValue, bool quotedValue,
int line) { int line) {
...@@ -907,7 +916,7 @@ void testConvError( ...@@ -907,7 +916,7 @@ void testConvError(
testConvError( \ testConvError( \
[&] { return expr; }, \ [&] { return expr; }, \
#expr, \ #expr, \
ConversionError::code, \ ConversionCode::code, \
value, \ value, \
quoted, \ quoted, \
__LINE__) __LINE__)
...@@ -1026,6 +1035,91 @@ TEST(Conv, ConversionErrorFloatToInt) { ...@@ -1026,6 +1035,91 @@ TEST(Conv, ConversionErrorFloatToInt) {
EXPECT_CONV_ERROR_ARITH(int8_t, 65.5, ARITH_LOSS_OF_PRECISION); EXPECT_CONV_ERROR_ARITH(int8_t, 65.5, ARITH_LOSS_OF_PRECISION);
} }
TEST(Conv, TryStringToBool) {
auto rv1 = folly::tryTo<bool>("xxxx");
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<bool>("false");
EXPECT_TRUE(rv2.hasValue());
EXPECT_FALSE(rv2.value());
auto rv3 = folly::tryTo<bool>("yes");
EXPECT_TRUE(rv3.hasValue());
EXPECT_TRUE(rv3.value());
}
TEST(Conv, TryStringToInt) {
auto rv1 = folly::tryTo<int>("1000000000000000000000000000000");
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<int>("4711");
EXPECT_TRUE(rv2.hasValue());
EXPECT_EQ(rv2.value(), 4711);
}
TEST(Conv, TryStringToFloat) {
auto rv1 = folly::tryTo<float>("");
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<float>("3.14");
EXPECT_TRUE(rv2.hasValue());
EXPECT_NEAR(rv2.value(), 3.14, 1e-5);
}
TEST(Conv, TryStringToDouble) {
auto rv1 = folly::tryTo<double>("");
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<double>("3.14");
EXPECT_TRUE(rv2.hasValue());
EXPECT_NEAR(rv2.value(), 3.14, 1e-10);
}
TEST(Conv, TryIntToInt) {
auto rv1 = folly::tryTo<uint8_t>(256);
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<uint8_t>(255);
EXPECT_TRUE(rv2.hasValue());
EXPECT_EQ(rv2.value(), 255);
}
TEST(Conv, TryFloatToFloat) {
auto rv1 = folly::tryTo<float>(1e100);
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<double>(25.5f);
EXPECT_TRUE(rv2.hasValue());
EXPECT_NEAR(rv2.value(), 25.5, 1e-10);
}
TEST(Conv, TryFloatToInt) {
auto rv1 = folly::tryTo<int>(100.001);
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<int>(100.0);
EXPECT_TRUE(rv2.hasValue());
EXPECT_EQ(rv2.value(), 100);
}
TEST(Conv, TryIntToFloat) {
auto rv1 = folly::tryTo<float>(std::numeric_limits<uint64_t>::max());
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<float>(1000ULL);
EXPECT_TRUE(rv2.hasValue());
EXPECT_EQ(rv2.value(), 1000.0f);
}
TEST(Conv, TryPtrPairToInt) {
StringPiece sp1("1000000000000000000000000000000");
auto rv1 = folly::tryTo<int>(sp1.begin(), sp1.end());
EXPECT_FALSE(rv1.hasValue());
StringPiece sp2("4711");
auto rv2 = folly::tryTo<int>(sp2.begin(), sp2.end());
EXPECT_TRUE(rv2.hasValue());
EXPECT_EQ(rv2.value(), 4711);
StringPiece sp3("-4711");
auto rv3 = folly::tryTo<int>(sp3.begin(), sp3.end());
EXPECT_TRUE(rv3.hasValue());
EXPECT_EQ(rv3.value(), -4711);
StringPiece sp4("4711");
auto rv4 = folly::tryTo<uint16_t>(sp4.begin(), sp4.end());
EXPECT_TRUE(rv4.hasValue());
EXPECT_EQ(rv4.value(), 4711);
}
TEST(Conv, NewUint64ToString) { TEST(Conv, NewUint64ToString) {
char buf[21]; char buf[21];
...@@ -1091,10 +1185,12 @@ struct Dimensions { ...@@ -1091,10 +1185,12 @@ struct Dimensions {
} }
}; };
void parseTo(folly::StringPiece in, Dimensions& out) { Expected<StringPiece, ConversionCode> parseTo(
out.w = folly::to<int>(&in); folly::StringPiece in,
in.removePrefix("x"); Dimensions& out) {
out.h = folly::to<int>(&in); return parseTo(in, out.w)
.then([](StringPiece sp) { return sp.removePrefix("x"), sp; })
.then([&](StringPiece sp) { return parseTo(sp, out.h); });
} }
template <class String> template <class String>
...@@ -1117,3 +1213,10 @@ TEST(Conv, custom_kkproviders) { ...@@ -1117,3 +1213,10 @@ TEST(Conv, custom_kkproviders) {
EXPECT_GT(str.capacity(), 2000); EXPECT_GT(str.capacity(), 2000);
EXPECT_LT(str.capacity(), 2500); EXPECT_LT(str.capacity(), 2500);
} }
TEST(Conv, TryToThenWithVoid) {
auto x = tryTo<int>("42").then([](int) {});
EXPECT_TRUE(x.hasValue());
Unit u = x.value();
(void)u;
}
...@@ -921,14 +921,27 @@ enum class Color { ...@@ -921,14 +921,27 @@ enum class Color {
Blue, Blue,
}; };
void parseTo(folly::StringPiece in, Color& out) { enum class ColorErrorCode { INVALID_COLOR };
struct ColorError : std::runtime_error {
using std::runtime_error::runtime_error;
};
ColorError makeConversionError(ColorErrorCode, StringPiece sp) {
return ColorError("Invalid my::Color representation : " + sp.str());
}
Expected<StringPiece, ColorErrorCode> parseTo(
StringPiece in,
Color& out) noexcept {
if (in == "R") { if (in == "R") {
out = Color::Red; out = Color::Red;
} else if (in == "B") { } else if (in == "B") {
out = Color::Blue; out = Color::Blue;
} else { } else {
throw runtime_error(""); return makeUnexpected(ColorErrorCode::INVALID_COLOR);
} }
return StringPiece(in.end(), in.end());
} }
} }
...@@ -938,6 +951,8 @@ TEST(Split, fixed_convert_custom) { ...@@ -938,6 +951,8 @@ TEST(Split, fixed_convert_custom) {
EXPECT_TRUE(folly::split(',', "R,B", c1, c2)); EXPECT_TRUE(folly::split(',', "R,B", c1, c2));
EXPECT_EQ(c1, my::Color::Red); EXPECT_EQ(c1, my::Color::Red);
EXPECT_EQ(c2, my::Color::Blue); EXPECT_EQ(c2, my::Color::Blue);
EXPECT_THROW(folly::split(',', "B,G", c1, c2), my::ColorError);
} }
TEST(String, join) { TEST(String, join) {
......
...@@ -52,7 +52,7 @@ TEST(Traits, scalars) { ...@@ -52,7 +52,7 @@ TEST(Traits, scalars) {
TEST(Traits, containers) { TEST(Traits, containers) {
EXPECT_TRUE (IsRelocatable<vector<F1>>::value); EXPECT_TRUE (IsRelocatable<vector<F1>>::value);
EXPECT_TRUE ((IsRelocatable<pair<F1, F1>>::value)); EXPECT_TRUE((IsRelocatable<pair<F1, F1>>::value));
EXPECT_TRUE ((IsRelocatable<pair<T1, T2>>::value)); EXPECT_TRUE ((IsRelocatable<pair<T1, T2>>::value));
} }
......
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