Commit ad2f228a authored by Eric Niebler's avatar Eric Niebler Committed by Facebook Github Bot 0

Reverted commit D3557832

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: 21f06b3c1a72b06dcf867ed3a3410f51e0fdaf45
parent 89cc3293
...@@ -207,10 +207,8 @@ struct ErrorString { ...@@ -207,10 +207,8 @@ struct ErrorString {
bool quote; bool quote;
}; };
// Keep this in sync with ConversionCode in Conv.h // Keep this in sync with ConversionError::Code in Conv.h
constexpr const std::array< constexpr const std::array<ErrorString, ConversionError::NUM_ERROR_CODES>
ErrorString,
static_cast<std::size_t>(ConversionCode::NUM_ERROR_CODES)>
kErrorStrings{{ kErrorStrings{{
{"Success", true}, {"Success", true},
{"Empty input string", true}, {"Empty input string", true},
...@@ -257,11 +255,34 @@ inline bool bool_str_cmp(const char** b, size_t len, const char* value) { ...@@ -257,11 +255,34 @@ inline bool bool_str_cmp(const char** b, size_t len, const char* value) {
} // anonymous namespace } // anonymous namespace
Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept { ConversionError makeConversionError(
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 makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); return ConversionResult<bool>(ConversionError::EMPTY_INPUT_STRING);
} }
if (!std::isspace(*b)) { if (!std::isspace(*b)) {
break; break;
...@@ -276,7 +297,7 @@ Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept { ...@@ -276,7 +297,7 @@ Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept {
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 makeUnexpected(ConversionCode::BOOL_OVERFLOW); return ConversionResult<bool>(ConversionError::BOOL_OVERFLOW);
} }
result = (*b == '1'); result = (*b == '1');
} }
...@@ -317,16 +338,16 @@ Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept { ...@@ -317,16 +338,16 @@ Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept {
} else if (bool_str_cmp(&b, len, "off")) { } else if (bool_str_cmp(&b, len, "off")) {
result = false; result = false;
} else { } else {
return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE); return ConversionResult<bool>(ConversionError::BOOL_INVALID_VALUE);
} }
break; break;
default: default:
return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE); return ConversionResult<bool>(ConversionError::BOOL_INVALID_VALUE);
} }
src->assign(b, e); src->assign(b, e);
return result; return ConversionResult<bool>(result);
} }
/** /**
...@@ -334,7 +355,7 @@ Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept { ...@@ -334,7 +355,7 @@ Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept {
* StringPiece parameter to munch the already-parsed characters. * StringPiece parameter to munch the already-parsed characters.
*/ */
template <class Tgt> template <class Tgt>
Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept { ConversionResult<Tgt> str_to_floating(StringPiece* src) {
using namespace double_conversion; using namespace double_conversion;
static StringToDoubleConverter static StringToDoubleConverter
conv(StringToDoubleConverter::ALLOW_TRAILING_JUNK conv(StringToDoubleConverter::ALLOW_TRAILING_JUNK
...@@ -345,7 +366,7 @@ Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept { ...@@ -345,7 +366,7 @@ Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept {
nullptr, nullptr); nullptr, nullptr);
if (src->empty()) { if (src->empty()) {
return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); return ConversionResult<Tgt>(ConversionError::EMPTY_INPUT_STRING);
} }
int length; int length;
...@@ -362,10 +383,10 @@ Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept { ...@@ -362,10 +383,10 @@ Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept {
// 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 makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); return ConversionResult<Tgt>(ConversionError::EMPTY_INPUT_STRING);
} }
src->advance(length); src->advance(length);
return result; return ConversionResult<Tgt>(result);
} }
auto* e = src->end(); auto* e = src->end();
...@@ -414,7 +435,7 @@ Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept { ...@@ -414,7 +435,7 @@ Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept {
if (result == 0.0) { if (result == 0.0) {
// All bets are off // All bets are off
return makeUnexpected(ConversionCode::STRING_TO_FLOAT_ERROR); return ConversionResult<Tgt>(ConversionError::STRING_TO_FLOAT_ERROR);
} }
if (negative) { if (negative) {
...@@ -423,13 +444,11 @@ Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept { ...@@ -423,13 +444,11 @@ Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept {
src->assign(b, e); src->assign(b, e);
return result; return ConversionResult<Tgt>(result);
} }
template Expected<float, ConversionCode> str_to_floating<float>( template ConversionResult<float> str_to_floating<float>(StringPiece* src);
StringPiece* src) noexcept; template ConversionResult<double> str_to_floating<double>(StringPiece* src);
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,
...@@ -441,39 +460,39 @@ class SignedValueHandler; ...@@ -441,39 +460,39 @@ class SignedValueHandler;
template <typename T> template <typename T>
class SignedValueHandler<T, true> { class SignedValueHandler<T, true> {
public: public:
ConversionCode init(const char*& b) { ConversionError::Code 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 ConversionCode::INVALID_LEADING_CHAR; return ConversionError::INVALID_LEADING_CHAR;
} }
++b; ++b;
} }
return ConversionCode::SUCCESS; return ConversionError::SUCCESS;
} }
ConversionCode overflow() { ConversionError::Code overflow() {
return negative_ ? ConversionCode::NEGATIVE_OVERFLOW return negative_ ? ConversionError::NEGATIVE_OVERFLOW
: ConversionCode::POSITIVE_OVERFLOW; : ConversionError::POSITIVE_OVERFLOW;
} }
template <typename U> template <typename U>
Expected<T, ConversionCode> finalize(U value) { ConversionResult<T> finalize(U value) {
T rv; T rv;
if (negative_) { if (negative_) {
rv = -value; rv = -value;
if (UNLIKELY(rv > 0)) { if (UNLIKELY(rv > 0)) {
return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW); return ConversionResult<T>(ConversionError::NEGATIVE_OVERFLOW);
} }
} else { } else {
rv = value; rv = value;
if (UNLIKELY(rv < 0)) { if (UNLIKELY(rv < 0)) {
return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW); return ConversionResult<T>(ConversionError::POSITIVE_OVERFLOW);
} }
} }
return rv; return ConversionResult<T>(rv);
} }
private: private:
...@@ -484,16 +503,16 @@ class SignedValueHandler<T, true> { ...@@ -484,16 +503,16 @@ class SignedValueHandler<T, true> {
template <typename T> template <typename T>
class SignedValueHandler<T, false> { class SignedValueHandler<T, false> {
public: public:
ConversionCode init(const char*&) { ConversionError::Code init(const char*&) {
return ConversionCode::SUCCESS; return ConversionError::SUCCESS;
} }
ConversionCode overflow() { ConversionError::Code overflow() {
return ConversionCode::POSITIVE_OVERFLOW; return ConversionError::POSITIVE_OVERFLOW;
} }
Expected<T, ConversionCode> finalize(T value) { ConversionResult<T> finalize(T value) {
return value; return ConversionResult<T>(value);
} }
}; };
...@@ -505,17 +524,15 @@ class SignedValueHandler<T, false> { ...@@ -505,17 +524,15 @@ class SignedValueHandler<T, false> {
* an appropriate error. * an appropriate error.
*/ */
template <class Tgt> template <class Tgt>
inline Expected<Tgt, ConversionCode> digits_to( inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) {
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 != ConversionCode::SUCCESS)) { if (UNLIKELY(err != ConversionError::SUCCESS)) {
return makeUnexpected(err); return ConversionResult<Tgt>(err);
} }
size_t size = e - b; size_t size = e - b;
...@@ -528,7 +545,7 @@ inline Expected<Tgt, ConversionCode> digits_to( ...@@ -528,7 +545,7 @@ inline Expected<Tgt, ConversionCode> digits_to(
if (b < e && *b == '0') { if (b < e && *b == '0') {
for (++b;; ++b) { for (++b;; ++b) {
if (b == e) { if (b == e) {
return Tgt(0); // just zeros, e.g. "0000" return ConversionResult<Tgt>(Tgt(0)); // just zeros, e.g. "0000"
} }
if (*b != '0') { if (*b != '0') {
size = e - b; size = e - b;
...@@ -539,7 +556,7 @@ inline Expected<Tgt, ConversionCode> digits_to( ...@@ -539,7 +556,7 @@ inline Expected<Tgt, ConversionCode> digits_to(
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 makeUnexpected(sgn.overflow()); return ConversionResult<Tgt>(sgn.overflow());
} }
} }
...@@ -594,7 +611,7 @@ inline Expected<Tgt, ConversionCode> digits_to( ...@@ -594,7 +611,7 @@ inline Expected<Tgt, ConversionCode> digits_to(
default: default:
assert(b == e); assert(b == e);
if (size == 0) { if (size == 0) {
return makeUnexpected(ConversionCode::NO_DIGITS); return ConversionResult<Tgt>(ConversionError::NO_DIGITS);
} }
break; break;
} }
...@@ -602,52 +619,46 @@ inline Expected<Tgt, ConversionCode> digits_to( ...@@ -602,52 +619,46 @@ inline Expected<Tgt, ConversionCode> digits_to(
return sgn.finalize(result); return sgn.finalize(result);
outOfRange: outOfRange:
return makeUnexpected(ConversionCode::NON_DIGIT_CHAR); return ConversionResult<Tgt>(ConversionError::NON_DIGIT_CHAR);
} }
template Expected<char, ConversionCode> digits_to<char>( template ConversionResult<char> digits_to<char>(const char*, const char*);
const char*, template ConversionResult<signed char> digits_to<signed char>(
const char*) noexcept;
template Expected<signed char, ConversionCode> digits_to<signed char>(
const char*, const char*,
const char*) noexcept; const char*);
template Expected<unsigned char, ConversionCode> digits_to<unsigned char>( template ConversionResult<unsigned char> digits_to<unsigned char>(
const char*, const char*,
const char*) noexcept; const char*);
template Expected<short, ConversionCode> digits_to<short>( template ConversionResult<short> digits_to<short>(const char*, const char*);
const char*, template ConversionResult<unsigned short> digits_to<unsigned short>(
const char*) noexcept;
template Expected<unsigned short, ConversionCode> digits_to<unsigned short>(
const char*, const char*,
const char*) noexcept; const char*);
template Expected<int, ConversionCode> digits_to<int>( template ConversionResult<int> digits_to<int>(const char*, const char*);
template ConversionResult<unsigned int> digits_to<unsigned int>(
const char*, const char*,
const char*) noexcept; const char*);
template Expected<unsigned int, ConversionCode> digits_to<unsigned int>(
const char*,
const char*) noexcept;
template Expected<long, ConversionCode> digits_to<long>( template ConversionResult<long> digits_to<long>(const char*, const char*);
template ConversionResult<unsigned long> digits_to<unsigned long>(
const char*, const char*,
const char*); const char*);
template Expected<unsigned long, ConversionCode> digits_to<unsigned long>(
const char*,
const char*) noexcept;
template Expected<long long, ConversionCode> digits_to<long long>( template ConversionResult<long long> digits_to<long long>(
const char*,
const char*);
template ConversionResult<unsigned long long> digits_to<unsigned long long>(
const char*, const char*,
const char*) noexcept; const char*);
template Expected<unsigned long long, ConversionCode>
digits_to<unsigned long long>(const char*, const char*) noexcept;
#if FOLLY_HAVE_INT128_T #if FOLLY_HAVE_INT128_T
template Expected<__int128, ConversionCode> digits_to<__int128>( template ConversionResult<__int128> digits_to<__int128>(
const char*,
const char*);
template ConversionResult<unsigned __int128> digits_to<unsigned __int128>(
const char*, const char*,
const char*) noexcept; const char*);
template Expected<unsigned __int128, ConversionCode>
digits_to<unsigned __int128>(const char*, const char*) noexcept;
#endif #endif
/** /**
...@@ -655,14 +666,14 @@ digits_to<unsigned __int128>(const char*, const char*) noexcept; ...@@ -655,14 +666,14 @@ digits_to<unsigned __int128>(const char*, const char*) noexcept;
* StringPiece parameter to munch the already-parsed characters. * StringPiece parameter to munch the already-parsed characters.
*/ */
template <class Tgt> template <class Tgt>
Expected<Tgt, ConversionCode> str_to_integral(StringPiece* src) noexcept { ConversionResult<Tgt> str_to_integral(StringPiece* src) {
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 makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); return ConversionResult<Tgt>(ConversionError::EMPTY_INPUT_STRING);
} }
if (!std::isspace(*b)) { if (!std::isspace(*b)) {
break; break;
...@@ -672,93 +683,63 @@ Expected<Tgt, ConversionCode> str_to_integral(StringPiece* src) noexcept { ...@@ -672,93 +683,63 @@ Expected<Tgt, ConversionCode> str_to_integral(StringPiece* src) noexcept {
SignedValueHandler<Tgt> sgn; SignedValueHandler<Tgt> sgn;
auto err = sgn.init(b); auto err = sgn.init(b);
if (UNLIKELY(err != ConversionCode::SUCCESS)) { if (UNLIKELY(err != ConversionError::SUCCESS)) {
return makeUnexpected(err); return ConversionResult<Tgt>(err);
} }
if (std::is_signed<Tgt>::value && UNLIKELY(b >= past)) { if (std::is_signed<Tgt>::value && UNLIKELY(b >= past)) {
return makeUnexpected(ConversionCode::NO_DIGITS); return ConversionResult<Tgt>(ConversionError::NO_DIGITS);
} }
if (UNLIKELY(!isdigit(*b))) { if (UNLIKELY(!isdigit(*b))) {
return makeUnexpected(ConversionCode::NON_DIGIT_CHAR); return ConversionResult<Tgt>(ConversionError::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.hasValue())) { if (UNLIKELY(!tmp.success())) {
return makeUnexpected( return ConversionResult<Tgt>(
tmp.error() == ConversionCode::POSITIVE_OVERFLOW ? sgn.overflow() tmp.error == ConversionError::POSITIVE_OVERFLOW ? sgn.overflow()
: tmp.error()); : tmp.error);
} }
auto res = sgn.finalize(tmp.value()); auto res = sgn.finalize(tmp.value);
if (res.hasValue()) { if (res.success()) {
src->advance(m - src->data()); src->advance(m - src->data());
} }
return res; return res;
} }
template Expected<char, ConversionCode> str_to_integral<char>( template ConversionResult<char> str_to_integral<char>(StringPiece* src);
StringPiece* src) noexcept; template ConversionResult<signed char> str_to_integral<signed char>(
template Expected<signed char, ConversionCode> str_to_integral<signed char>( StringPiece* src);
StringPiece* src) noexcept; template ConversionResult<unsigned char> str_to_integral<unsigned char>(
template Expected<unsigned char, ConversionCode> str_to_integral<unsigned char>( StringPiece* src);
StringPiece* src) noexcept;
template ConversionResult<short> str_to_integral<short>(StringPiece* src);
template Expected<short, ConversionCode> str_to_integral<short>( template ConversionResult<unsigned short> str_to_integral<unsigned short>(
StringPiece* src) noexcept; StringPiece* src);
template Expected<unsigned short, ConversionCode>
str_to_integral<unsigned short>(StringPiece* src) noexcept; template ConversionResult<int> str_to_integral<int>(StringPiece* src);
template ConversionResult<unsigned int> str_to_integral<unsigned int>(
template Expected<int, ConversionCode> str_to_integral<int>( StringPiece* src);
StringPiece* src) noexcept;
template Expected<unsigned int, ConversionCode> str_to_integral<unsigned int>( template ConversionResult<long> str_to_integral<long>(StringPiece* src);
StringPiece* src) noexcept; template ConversionResult<unsigned long> str_to_integral<unsigned long>(
StringPiece* src);
template Expected<long, ConversionCode> str_to_integral<long>(
StringPiece* src) noexcept; template ConversionResult<long long> str_to_integral<long long>(
template Expected<unsigned long, ConversionCode> str_to_integral<unsigned long>( StringPiece* src);
StringPiece* src) noexcept; template ConversionResult<unsigned long long>
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 Expected<__int128, ConversionCode> str_to_integral<__int128>( template ConversionResult<__int128> str_to_integral<__int128>(StringPiece* src);
StringPiece* src) noexcept; template ConversionResult<unsigned __int128> str_to_integral<unsigned __int128>(
template Expected<unsigned __int128, ConversionCode> StringPiece* src);
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,88 +39,75 @@ ...@@ -39,88 +39,75 @@
#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 {
// Keep this in sync with kErrorStrings in Conv.cpp class ConversionError : public std::range_error {
enum class ConversionCode : unsigned char {
SUCCESS,
EMPTY_INPUT_STRING,
NO_DIGITS,
BOOL_OVERFLOW,
BOOL_INVALID_VALUE,
NON_DIGIT_CHAR,
INVALID_LEADING_CHAR,
POSITIVE_OVERFLOW,
NEGATIVE_OVERFLOW,
STRING_TO_FLOAT_ERROR,
NON_WHITESPACE_AFTER_END,
ARITH_POSITIVE_OVERFLOW,
ARITH_NEGATIVE_OVERFLOW,
ARITH_LOSS_OF_PRECISION,
NUM_ERROR_CODES, // has to be the last entry
};
struct ConversionErrorBase : std::range_error {
using std::range_error::range_error;
};
class ConversionError : public ConversionErrorBase {
public: public:
ConversionError(const std::string& str, ConversionCode code) // Keep this in sync with kErrorStrings in Conv.cpp
: ConversionErrorBase(str), code_(code) {} enum Code {
SUCCESS,
EMPTY_INPUT_STRING,
NO_DIGITS,
BOOL_OVERFLOW,
BOOL_INVALID_VALUE,
NON_DIGIT_CHAR,
INVALID_LEADING_CHAR,
POSITIVE_OVERFLOW,
NEGATIVE_OVERFLOW,
STRING_TO_FLOAT_ERROR,
NON_WHITESPACE_AFTER_END,
ARITH_POSITIVE_OVERFLOW,
ARITH_NEGATIVE_OVERFLOW,
ARITH_LOSS_OF_PRECISION,
NUM_ERROR_CODES, // has to be the last entry
};
ConversionError(const char* str, ConversionCode code) ConversionError(const std::string& str, Code code)
: ConversionErrorBase(str), code_(code) {} : std::range_error(str), code_(code) {}
ConversionCode errorCode() const { ConversionError(const char* str, Code code)
return code_; : std::range_error(str), code_(code) {}
}
Code errorCode() const { return code_; }
private: private:
ConversionCode code_; Code code_;
}; };
/*******************************************************************************
* Custom Error Translation
*
* Your overloaded parseTo() function can return a custom error code on failure.
* ::folly::to() will call makeConversionError to translate that error code into
* an object to throw. makeConversionError is found by argument-dependent
* lookup. It should have this signature:
*
* namespace other_namespace {
* enum YourErrorCode { BAD_ERROR, WORSE_ERROR };
*
* struct YourConversionError : ConversionErrorBase {
* YourConversionError(const char* what) : ConversionErrorBase(what) {}
* };
*
* YourConversionError
* makeConversionError(YourErrorCode code, ::folly::StringPiece sp) {
* ...
* return YourConversionError(messageString);
* }
******************************************************************************/
ConversionError makeConversionError(ConversionCode code, StringPiece sp);
namespace detail { namespace detail {
ConversionError makeConversionError(
ConversionError::Code code,
const char* input,
size_t inputLen);
inline ConversionError makeConversionError(
ConversionError::Code code,
const std::string& str) {
return makeConversionError(code, str.data(), str.size());
}
inline ConversionError makeConversionError(
ConversionError::Code code,
StringPiece sp) {
return makeConversionError(code, sp.data(), sp.size());
}
/** /**
* 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 ConversionCode enforceWhitespaceErr(StringPiece sp) { inline ConversionError::Code enforceWhitespaceErr(StringPiece sp) {
for (auto c : sp) { for (auto c : sp) {
if (UNLIKELY(!std::isspace(c))) { if (!std::isspace(c)) {
return ConversionCode::NON_WHITESPACE_AFTER_END; return ConversionError::NON_WHITESPACE_AFTER_END;
} }
} }
return ConversionCode::SUCCESS; return ConversionError::SUCCESS;
} }
/** /**
...@@ -128,57 +115,42 @@ inline ConversionCode enforceWhitespaceErr(StringPiece sp) { ...@@ -128,57 +115,42 @@ inline ConversionCode enforceWhitespaceErr(StringPiece sp) {
*/ */
inline void enforceWhitespace(StringPiece sp) { inline void enforceWhitespace(StringPiece sp) {
auto err = enforceWhitespaceErr(sp); auto err = enforceWhitespaceErr(sp);
if (err != ConversionCode::SUCCESS) { if (err != ConversionError::SUCCESS) {
throw makeConversionError(err, sp); throw detail::makeConversionError(err, sp);
} }
} }
}
/** /**
* The identity conversion function. * A simple std::pair-like wrapper to wrap both a value and an error
* tryTo<T>(T) returns itself for all types T.
*/ */
template <class Tgt, class Src> template <typename T>
typename std::enable_if< struct ConversionResult {
std::is_same<Tgt, typename std::decay<Src>::type>::value, explicit ConversionResult(T v) : value(v) {}
Expected<Tgt, ConversionCode>>::type explicit ConversionResult(ConversionError::Code e) : error(e) {}
tryTo(Src&& value) {
return std::forward<Src>(value);
}
template <class Tgt, class Src> bool success() const {
typename std::enable_if< return error == ConversionError::SUCCESS;
std::is_same<Tgt, typename std::decay<Src>::type>::value, }
Tgt>::type
to(Src&& value) {
return std::forward<Src>(value);
}
/******************************************************************************* T value;
* Arithmetic to boolean ConversionError::Code error{ConversionError::SUCCESS};
******************************************************************************/ };
}
/** /**
* Unchecked conversion from arithmetic to boolean. This is different from the * The identity conversion function.
* other arithmetic conversions because we use the C convention of treating any * to<T>(T) returns itself for all types T.
* non-zero value as true, instead of range checking.
*/ */
template <class Tgt, class Src> template <class Tgt, class Src>
typename std::enable_if< typename std::enable_if<std::is_same<Tgt, Src>::value, Tgt>::type
std::is_arithmetic<Src>::value && !std::is_same<Tgt, Src>::value && to(const Src & value) {
std::is_same<Tgt, bool>::value, return 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< typename std::enable_if<std::is_same<Tgt, Src>::value, Tgt>::type
std::is_arithmetic<Src>::value && !std::is_same<Tgt, Src>::value && to(Src && value) {
std::is_same<Tgt, bool>::value, return std::forward<Src>(value);
Tgt>::type
to(const Src& value) {
return value != Src();
} }
/******************************************************************************* /*******************************************************************************
...@@ -187,28 +159,34 @@ to(const Src& value) { ...@@ -187,28 +159,34 @@ to(const Src& value) {
namespace detail { namespace detail {
template <typename... Ts> template <class T>
struct LastElementImpl { const T& getLastElement(const T & v) {
static void call(Ignored<Ts>...) {} return v;
}; }
template <typename Head, typename... Ts>
struct LastElementImpl<Head, Ts...> {
template <typename Last>
static Last call(Ignored<Ts>..., Last&& last) {
return std::forward<Last>(last);
}
};
template <typename... Ts> template <class T, class... Ts>
auto getLastElement(const Ts&... ts) typename std::tuple_element<
-> decltype(LastElementImpl<Ts...>::call(ts...)) { sizeof...(Ts),
return LastElementImpl<Ts...>::call(ts...); std::tuple<T, Ts...> >::type const&
getLastElement(const T&, const Ts&... vs) {
return getLastElement(vs...);
} }
// 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 LastElement : std::decay<decltype( struct last_element {
LastElementImpl<Ts...>::call(std::declval<Ts>()...))> { typedef typename std::enable_if<
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
...@@ -395,10 +373,11 @@ toAppend(Src value, Tgt * result) { ...@@ -395,10 +373,11 @@ toAppend(Src value, Tgt * result) {
} }
} }
template <class Src> template<class Src>
typename std::enable_if<std::is_convertible<Src, const char*>::value, size_t>:: typename std::enable_if<
type std::is_convertible<Src, const char*>::value,
estimateSpaceNeeded(Src value) { size_t>::type
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();
...@@ -768,10 +747,11 @@ toAppendStrImpl(const T& v, Tgt result) { ...@@ -768,10 +747,11 @@ toAppendStrImpl(const T& v, Tgt result) {
} }
template <class T, class... Ts> template <class T, class... Ts>
typename std::enable_if< typename std::enable_if<sizeof...(Ts) >= 2
sizeof...(Ts) >= 2 && && IsSomeString<
IsSomeString<typename std::remove_pointer< typename std::remove_pointer<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type typename detail::last_element<Ts...>::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...);
...@@ -785,10 +765,11 @@ toAppendDelimStrImpl(const Delimiter& /* delim */, const T& v, Tgt result) { ...@@ -785,10 +765,11 @@ 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< typename std::enable_if<sizeof...(Ts) >= 2
sizeof...(Ts) >= 2 && && IsSomeString<
IsSomeString<typename std::remove_pointer< typename std::remove_pointer<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type typename detail::last_element<Ts...>::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
...@@ -822,10 +803,11 @@ toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) { ...@@ -822,10 +803,11 @@ toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) {
* } * }
*/ */
template <class... Ts> template <class... Ts>
typename std::enable_if< typename std::enable_if<sizeof...(Ts) >= 3
sizeof...(Ts) >= 3 && && IsSomeString<
IsSomeString<typename std::remove_pointer< typename std::remove_pointer<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type typename detail::last_element<Ts...>::type
>::type>::value>::type
toAppend(const Ts&... vs) { toAppend(const Ts&... vs) {
::folly::detail::toAppendStrImpl(vs...); ::folly::detail::toAppendStrImpl(vs...);
} }
...@@ -851,8 +833,11 @@ void toAppend(const pid_t a, Tgt* res) { ...@@ -851,8 +833,11 @@ 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<IsSomeString<typename std::remove_pointer< typename std::enable_if<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type IsSomeString<
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...);
...@@ -889,10 +874,11 @@ typename std::enable_if<IsSomeString<Tgt>::value>::type toAppendDelim( ...@@ -889,10 +874,11 @@ 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< typename std::enable_if<sizeof...(Ts) >= 3
sizeof...(Ts) >= 3 && && IsSomeString<
IsSomeString<typename std::remove_pointer< typename std::remove_pointer<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type typename detail::last_element<Ts...>::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...);
} }
...@@ -901,8 +887,11 @@ toAppendDelim(const Delimiter& delim, const Ts&... vs) { ...@@ -901,8 +887,11 @@ 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<IsSomeString<typename std::remove_pointer< typename std::enable_if<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type IsSomeString<
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...);
...@@ -917,11 +906,10 @@ void toAppendDelimFit(const De&, const Ts&) {} ...@@ -917,11 +906,10 @@ 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::LastElement<const Ts&...>::type>:: !std::is_same<Tgt, typename detail::last_element<Ts...>::type>::value),
value), Tgt>::type
Tgt>::type
to(const Ts&... vs) { to(const Ts&... vs) {
Tgt result; Tgt result;
toAppendFit(vs..., &result); toAppendFit(vs..., &result);
...@@ -945,11 +933,10 @@ toDelim(const Delim& /* delim */, const Src& value) { ...@@ -945,11 +933,10 @@ 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::LastElement<const Ts&...>::type>:: !std::is_same<Tgt, typename detail::last_element<Ts...>::type>::value),
value), Tgt>::type
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);
...@@ -962,121 +949,130 @@ toDelim(const Delim& delim, const Ts&... vs) { ...@@ -962,121 +949,130 @@ toDelim(const Delim& delim, const Ts&... vs) {
namespace detail { namespace detail {
Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept; ConversionResult<bool> str_to_bool(StringPiece* src);
template <typename T> template <typename T>
Expected<T, ConversionCode> str_to_floating(StringPiece* src) noexcept; ConversionResult<T> str_to_floating(StringPiece* src);
extern template Expected<float, ConversionCode> str_to_floating<float>( extern template ConversionResult<float> str_to_floating<float>(
StringPiece* src) noexcept; StringPiece* src);
extern template Expected<double, ConversionCode> str_to_floating<double>( extern template ConversionResult<double> str_to_floating<double>(
StringPiece* src) noexcept; StringPiece* src);
template <class Tgt> template <class Tgt>
Expected<Tgt, ConversionCode> digits_to(const char* b, const char* e) noexcept; ConversionResult<Tgt> digits_to(const char* b, const char* e);
extern template Expected<char, ConversionCode> digits_to<char>( extern template ConversionResult<char> digits_to<char>(
const char*, const char*,
const char*) noexcept; const char*);
extern template Expected<signed char, ConversionCode> digits_to<signed char>( extern template ConversionResult<signed char> digits_to<signed char>(
const char*, const char*,
const char*) noexcept; const char*);
extern template Expected<unsigned char, ConversionCode> extern template ConversionResult<unsigned char> digits_to<unsigned char>(
digits_to<unsigned char>(const char*, const char*) noexcept;
extern template Expected<short, ConversionCode> digits_to<short>(
const char*, const char*,
const char*) noexcept; const char*);
extern template Expected<unsigned short, ConversionCode>
digits_to<unsigned short>(const char*, const char*) noexcept;
extern template Expected<int, ConversionCode> digits_to<int>( extern template ConversionResult<short> digits_to<short>(
const char*, const char*,
const char*) noexcept; const char*);
extern template Expected<unsigned int, ConversionCode> digits_to<unsigned int>( extern template ConversionResult<unsigned short> digits_to<unsigned short>(
const char*, const char*,
const char*) noexcept; const char*);
extern template Expected<long, ConversionCode> digits_to<long>( extern template ConversionResult<int> digits_to<int>(const char*, const char*);
extern template ConversionResult<unsigned int> digits_to<unsigned int>(
const char*, const char*,
const char*) noexcept; const char*);
extern template Expected<unsigned long, ConversionCode>
digits_to<unsigned long>(const char*, const char*) noexcept;
extern template Expected<long long, ConversionCode> digits_to<long long>( extern template ConversionResult<long> digits_to<long>(
const char*, const char*,
const char*) noexcept; const char*);
extern template Expected<unsigned long long, ConversionCode> extern template ConversionResult<unsigned long> digits_to<unsigned long>(
digits_to<unsigned long long>(const char*, const char*) noexcept; const char*,
const char*);
extern template ConversionResult<long long> digits_to<long long>(
const char*,
const char*);
extern template ConversionResult<unsigned long long>
digits_to<unsigned long long>(const char*, const char*);
#if FOLLY_HAVE_INT128_T #if FOLLY_HAVE_INT128_T
extern template Expected<__int128, ConversionCode> digits_to<__int128>( extern template ConversionResult<__int128> digits_to<__int128>(
const char*, const char*,
const char*) noexcept; const char*);
extern template Expected<unsigned __int128, ConversionCode> extern template ConversionResult<unsigned __int128>
digits_to<unsigned __int128>(const char*, const char*) noexcept; digits_to<unsigned __int128>(const char*, const char*);
#endif #endif
template <class T> template <class T>
Expected<T, ConversionCode> str_to_integral(StringPiece* src) noexcept; ConversionResult<T> str_to_integral(StringPiece* src);
extern template Expected<char, ConversionCode> str_to_integral<char>( extern template ConversionResult<char> str_to_integral<char>(StringPiece* src);
StringPiece* src) noexcept; extern template ConversionResult<signed char> str_to_integral<signed char>(
extern template Expected<signed char, ConversionCode> StringPiece* src);
str_to_integral<signed char>(StringPiece* src) noexcept; extern template ConversionResult<unsigned char> str_to_integral<unsigned char>(
extern template Expected<unsigned char, ConversionCode> StringPiece* src);
str_to_integral<unsigned char>(StringPiece* src) noexcept;
extern template ConversionResult<short> str_to_integral<short>(
extern template Expected<short, ConversionCode> str_to_integral<short>( StringPiece* src);
StringPiece* src) noexcept; extern template ConversionResult<unsigned short>
extern template Expected<unsigned short, ConversionCode> str_to_integral<unsigned short>(StringPiece* src);
str_to_integral<unsigned short>(StringPiece* src) noexcept;
extern template ConversionResult<int> str_to_integral<int>(StringPiece* src);
extern template Expected<int, ConversionCode> str_to_integral<int>( extern template ConversionResult<unsigned int> str_to_integral<unsigned int>(
StringPiece* src) noexcept; StringPiece* src);
extern template Expected<unsigned int, ConversionCode>
str_to_integral<unsigned int>(StringPiece* src) noexcept; extern template ConversionResult<long> str_to_integral<long>(StringPiece* src);
extern template ConversionResult<unsigned long> str_to_integral<unsigned long>(
extern template Expected<long, ConversionCode> str_to_integral<long>( StringPiece* src);
StringPiece* src) noexcept;
extern template Expected<unsigned long, ConversionCode> extern template ConversionResult<long long> str_to_integral<long long>(
str_to_integral<unsigned long>(StringPiece* src) noexcept; StringPiece* src);
extern template ConversionResult<unsigned long long>
extern template Expected<long long, ConversionCode> str_to_integral<long long>( str_to_integral<unsigned long long>(StringPiece* src);
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 Expected<__int128, ConversionCode> str_to_integral<__int128>( extern template ConversionResult<__int128> str_to_integral<__int128>(
StringPiece* src) noexcept; StringPiece* src);
extern template Expected<unsigned __int128, ConversionCode> extern template ConversionResult<unsigned __int128>
str_to_integral<unsigned __int128>(StringPiece* src) noexcept; str_to_integral<unsigned __int128>(StringPiece* src);
#endif #endif
template <typename T> template <typename T>
typename std:: typename std::enable_if<std::is_same<T, bool>::value, ConversionResult<T>>::type
enable_if<std::is_same<T, bool>::value, Expected<T, ConversionCode>>::type convertTo(StringPiece* src) {
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, std::is_floating_point<T>::value, ConversionResult<T>>::type
Expected<T, ConversionCode>>::type convertTo(StringPiece* src) {
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,
Expected<T, ConversionCode>>::type ConversionResult<T>>::type
convertTo(StringPiece* src) noexcept { convertTo(StringPiece* src) {
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
/** /**
...@@ -1085,22 +1081,12 @@ convertTo(StringPiece* src) noexcept { ...@@ -1085,22 +1081,12 @@ convertTo(StringPiece* src) noexcept {
*/ */
template <typename Tgt> template <typename Tgt>
typename std::enable_if< typename std::enable_if<
std::is_integral<Tgt>::value && !std::is_same<Tgt, bool>::value, std::is_integral<typename detail::WrapperInfo<Tgt>::type>::value &&
Expected<Tgt, ConversionCode>>::type !std::is_same<typename detail::WrapperInfo<Tgt>::type, bool>::value,
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) {
return tryTo<Tgt>(b, e).thenOrThrow( auto res = detail::digits_to<typename detail::WrapperInfo<Tgt>::type>(b, e);
[](Tgt res) { return res; }, return detail::wrap<Tgt>(res, [&] { return StringPiece(b, e); });
[=](ConversionCode code) {
return makeConversionError(code, StringPiece(b, e));
});
} }
/******************************************************************************* /*******************************************************************************
...@@ -1108,21 +1094,47 @@ to(const char* b, const char* e) { ...@@ -1108,21 +1094,47 @@ to(const char* b, const char* e) {
******************************************************************************/ ******************************************************************************/
/** /**
* Parsing strings to numeric types. * Parsing strings to numeric types. These routines differ from
* parseTo(str, numeric) routines in that they take a POINTER TO a StringPiece
* and alter that StringPiece to reflect progress information.
*/ */
template <typename Tgt> template <typename Tgt>
FOLLY_WARN_UNUSED_RESULT inline typename std::enable_if< typename std::enable_if<
std::is_arithmetic<Tgt>::value, std::is_arithmetic<typename detail::WrapperInfo<Tgt>::type>::value>::type
Expected<StringPiece, ConversionCode>>::type parseTo(StringPiece* src, Tgt& out) {
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) {
return detail::convertTo<Tgt>(&src).then( auto res = detail::convertTo<typename detail::WrapperInfo<Tgt>::type>(&src);
[&](Tgt res) { return void(out = res), src; }); if (LIKELY(res.success())) {
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 {
/** /**
...@@ -1135,22 +1147,22 @@ typename std::enable_if< ...@@ -1135,22 +1147,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,
Expected<Tgt, ConversionCode>>::type ConversionResult<Tgt>>::type
convertTo(const Src& value) noexcept { convertTo(const Src& value) {
/* 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 makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW); return ConversionResult<Tgt>(ConversionError::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 makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW); return ConversionResult<Tgt>(ConversionError::ARITH_NEGATIVE_OVERFLOW);
} }
} }
return static_cast<Tgt>(value); return ConversionResult<Tgt>(static_cast<Tgt>(value));
} }
/** /**
...@@ -1162,18 +1174,18 @@ template <class Tgt, class Src> ...@@ -1162,18 +1174,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,
Expected<Tgt, ConversionCode>>::type ConversionResult<Tgt>>::type
convertTo(const Src& value) noexcept { convertTo(const Src& value) {
/* 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 makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW); return ConversionResult<Tgt>(ConversionError::ARITH_POSITIVE_OVERFLOW);
} }
if (value < std::numeric_limits<Tgt>::lowest()) { if (value < std::numeric_limits<Tgt>::lowest()) {
return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW); return ConversionResult<Tgt>(ConversionError::ARITH_NEGATIVE_OVERFLOW);
} }
} }
return boost::implicit_cast<Tgt>(value); return ConversionResult<Tgt>(boost::implicit_cast<Tgt>(value));
} }
/** /**
...@@ -1240,18 +1252,18 @@ template <typename Tgt, typename Src> ...@@ -1240,18 +1252,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),
Expected<Tgt, ConversionCode>>::type ConversionResult<Tgt>>::type
convertTo(const Src& value) noexcept { convertTo(const Src& value) {
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 result; return ConversionResult<Tgt>(result);
} }
} }
} }
return makeUnexpected(ConversionCode::ARITH_LOSS_OF_PRECISION); return ConversionResult<Tgt>(ConversionError::ARITH_LOSS_OF_PRECISION);
} }
template <typename Tgt, typename Src> template <typename Tgt, typename Src>
...@@ -1274,20 +1286,13 @@ using IsArithToArith = std::integral_constant< ...@@ -1274,20 +1286,13 @@ 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<Tgt, Src>::value, detail::IsArithToArith<
Expected<Tgt, ConversionCode>>::type typename detail::WrapperInfo<Tgt>::type, Src>::value, Tgt>::type
tryTo(const Src& value) noexcept { to(const Src& value) {
return detail::convertTo<Tgt>(value); auto res = detail::convertTo<typename detail::WrapperInfo<Tgt>::type>(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));
});
} }
/******************************************************************************* /*******************************************************************************
...@@ -1298,157 +1303,49 @@ typename std::enable_if<detail::IsArithToArith<Tgt, Src>::value, Tgt>::type to( ...@@ -1298,157 +1303,49 @@ typename std::enable_if<detail::IsArithToArith<Tgt, Src>::value, Tgt>::type to(
* argument-dependent lookup: * argument-dependent lookup:
* *
* namespace other_namespace { * namespace other_namespace {
* ::folly::Expected<::folly::StringPiece, SomeErrorCode> * void parseTo(::folly::StringPiece, OtherType&);
* parseTo(::folly::StringPiece, OtherType&) noexcept;
* } * }
******************************************************************************/ ******************************************************************************/
template <class T> template <class T>
FOLLY_WARN_UNUSED_RESULT typename std::enable_if< typename std::enable_if<std::is_enum<T>::value>::type
std::is_enum<T>::value, parseTo(StringPiece in, T& out) {
Expected<StringPiece, ConversionCode>>::type typename std::underlying_type<T>::type tmp;
parseTo(StringPiece in, T& out) noexcept { parseTo(in, tmp);
typename std::underlying_type<T>::type tmp{}; out = static_cast<T>(tmp);
auto restOrError = parseTo(in, tmp);
out = static_cast<T>(tmp); // Harmless if parseTo fails
return restOrError;
}
FOLLY_WARN_UNUSED_RESULT
inline Expected<StringPiece, ConversionCode> parseTo(
StringPiece in,
StringPiece& out) noexcept {
out = in;
return StringPiece{in.end(), in.end()};
} }
FOLLY_WARN_UNUSED_RESULT inline void parseTo(StringPiece in, StringPiece& out) {
inline Expected<StringPiece, ConversionCode> parseTo( out = in;
StringPiece in,
std::string& out) {
out.clear();
out.append(in.data(), in.size()); // TODO try/catch?
return StringPiece{in.end(), in.end()};
} }
FOLLY_WARN_UNUSED_RESULT inline void parseTo(StringPiece in, std::string& out) {
inline Expected<StringPiece, ConversionCode> parseTo(
StringPiece in,
fbstring& out) {
out.clear(); out.clear();
out.append(in.data(), in.size()); // TODO try/catch? out.append(in.data(), in.size());
return StringPiece{in.end(), in.end()};
} }
namespace detail { inline void parseTo(StringPiece in, fbstring& out) {
template <typename Tgt> out.clear();
using ParseToResult = decltype(parseTo(StringPiece{}, std::declval<Tgt&>())); out.append(in.data(), in.size());
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>
inline typename std::enable_if< typename std::enable_if<!std::is_same<StringPiece, Tgt>::value, Tgt>::type
!std::is_same<StringPiece, Tgt>::value, to(StringPiece src) {
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;
return parseTo(*src, result).then([&, src](StringPiece sp) -> Tgt { parseTo(src, result);
*src = sp; return result;
return std::move(result);
});
} }
template <class Tgt> template <class Tgt>
Tgt to(StringPiece* src) { Tgt to(StringPiece* src) {
Tgt result; Tgt result;
using Error = detail::ParseToError<Tgt>; parseTo(src, result);
return parseTo(*src, result) return result;
.thenOrThrow(
[&, src](StringPiece sp) -> Tgt {
*src = sp;
return std::move(result);
},
[=](Error e) { return makeConversionError(e, *src); });
} }
/******************************************************************************* /*******************************************************************************
...@@ -1457,27 +1354,8 @@ Tgt to(StringPiece* src) { ...@@ -1457,27 +1354,8 @@ 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, std::is_enum<Src>::value && !std::is_same<Src, Tgt>::value, Tgt>::type
Expected<Tgt, ConversionCode>>::type to(const Src & value) {
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,15 +92,6 @@ constexpr bool kHasUnalignedAccess = false; ...@@ -92,15 +92,6 @@ 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;
} }
output = folly::to<OutputType>(input); parseTo(input, output);
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...))) {
outHead = folly::to<OutputType>(head); parseTo(head, outHead);
return true; return true;
} }
return false; return false;
......
...@@ -491,8 +491,7 @@ struct IsConvertible { ...@@ -491,8 +491,7 @@ struct IsConvertible {
template <class T> template <class T>
struct IsConvertible< struct IsConvertible<
T, T,
decltype(static_cast<void>( decltype(parseTo(std::declval<folly::StringPiece>(), std::declval<T&>()))> {
parseTo(std::declval<folly::StringPiece>(), std::declval<T&>())))> {
enum { value = true }; enum { value = true };
}; };
......
...@@ -215,31 +215,3 @@ is returned, which can be tested for as follows: ...@@ -215,31 +215,3 @@ 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,16 +911,6 @@ inline void stringToTypeClassic(const char* str, uint32_t n) { ...@@ -911,16 +911,6 @@ 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) {
...@@ -934,16 +924,6 @@ inline void ptrPairToIntClassic(StringPiece sp, uint32_t n) { ...@@ -934,16 +924,6 @@ 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>
...@@ -964,24 +944,6 @@ inline size_t arithToArithClassic(const U* in, uint32_t numItems) { ...@@ -964,24 +944,6 @@ 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 {
...@@ -1018,12 +980,6 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}}; ...@@ -1018,12 +980,6 @@ 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) \
...@@ -1032,26 +988,14 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}}; ...@@ -1032,26 +988,14 @@ 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,7 +18,6 @@ ...@@ -18,7 +18,6 @@
#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>
...@@ -821,14 +820,6 @@ TEST(Conv, StringToBool) { ...@@ -821,14 +820,6 @@ 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));
...@@ -883,7 +874,7 @@ template <typename F> ...@@ -883,7 +874,7 @@ template <typename F>
void testConvError( void testConvError(
F&& expr, F&& expr,
const char* exprStr, const char* exprStr,
ConversionCode code, ConversionError::Code code,
const char* value, const char* value,
bool quotedValue, bool quotedValue,
int line) { int line) {
...@@ -916,7 +907,7 @@ void testConvError( ...@@ -916,7 +907,7 @@ void testConvError(
testConvError( \ testConvError( \
[&] { return expr; }, \ [&] { return expr; }, \
#expr, \ #expr, \
ConversionCode::code, \ ConversionError::code, \
value, \ value, \
quoted, \ quoted, \
__LINE__) __LINE__)
...@@ -1035,91 +1026,6 @@ TEST(Conv, ConversionErrorFloatToInt) { ...@@ -1035,91 +1026,6 @@ 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];
...@@ -1185,12 +1091,10 @@ struct Dimensions { ...@@ -1185,12 +1091,10 @@ struct Dimensions {
} }
}; };
Expected<StringPiece, ConversionCode> parseTo( void parseTo(folly::StringPiece in, Dimensions& out) {
folly::StringPiece in, out.w = folly::to<int>(&in);
Dimensions& out) { in.removePrefix("x");
return parseTo(in, out.w) out.h = folly::to<int>(&in);
.then([](StringPiece sp) { return sp.removePrefix("x"), sp; })
.then([&](StringPiece sp) { return parseTo(sp, out.h); });
} }
template <class String> template <class String>
...@@ -1213,10 +1117,3 @@ TEST(Conv, custom_kkproviders) { ...@@ -1213,10 +1117,3 @@ 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,27 +921,14 @@ enum class Color { ...@@ -921,27 +921,14 @@ enum class Color {
Blue, Blue,
}; };
enum class ColorErrorCode { INVALID_COLOR }; void parseTo(folly::StringPiece in, Color& out) {
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 {
return makeUnexpected(ColorErrorCode::INVALID_COLOR); throw runtime_error("");
} }
return StringPiece(in.end(), in.end());
} }
} }
...@@ -951,8 +938,6 @@ TEST(Split, fixed_convert_custom) { ...@@ -951,8 +938,6 @@ 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