Commit 4da4f331 authored by Eric Niebler's avatar Eric Niebler Committed by Facebook Github Bot

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: D3720512

fbshipit-source-id: dadb8db1b7d7ad8d3e80c1cc69c0480169f9217a
parent 5d242d14
......@@ -207,8 +207,10 @@ struct ErrorString {
bool quote;
};
// Keep this in sync with ConversionError::Code in Conv.h
constexpr const std::array<ErrorString, ConversionError::NUM_ERROR_CODES>
// Keep this in sync with ConversionCode in Conv.h
constexpr const std::array<
ErrorString,
static_cast<std::size_t>(ConversionCode::NUM_ERROR_CODES)>
kErrorStrings{{
{"Success", true},
{"Empty input string", true},
......@@ -255,34 +257,11 @@ inline bool bool_str_cmp(const char** b, size_t len, const char* value) {
} // anonymous namespace
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) {
Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept {
auto b = src->begin(), e = src->end();
for (;; ++b) {
if (b >= e) {
return ConversionResult<bool>(ConversionError::EMPTY_INPUT_STRING);
return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
}
if (!std::isspace(*b)) {
break;
......@@ -297,7 +276,7 @@ ConversionResult<bool> str_to_bool(StringPiece* src) {
result = false;
for (; b < e && isdigit(*b); ++b) {
if (result || (*b != '0' && *b != '1')) {
return ConversionResult<bool>(ConversionError::BOOL_OVERFLOW);
return makeUnexpected(ConversionCode::BOOL_OVERFLOW);
}
result = (*b == '1');
}
......@@ -338,16 +317,16 @@ ConversionResult<bool> str_to_bool(StringPiece* src) {
} else if (bool_str_cmp(&b, len, "off")) {
result = false;
} else {
return ConversionResult<bool>(ConversionError::BOOL_INVALID_VALUE);
return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE);
}
break;
default:
return ConversionResult<bool>(ConversionError::BOOL_INVALID_VALUE);
return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE);
}
src->assign(b, e);
return ConversionResult<bool>(result);
return result;
}
/**
......@@ -355,7 +334,7 @@ ConversionResult<bool> str_to_bool(StringPiece* src) {
* StringPiece parameter to munch the already-parsed characters.
*/
template <class Tgt>
ConversionResult<Tgt> str_to_floating(StringPiece* src) {
Expected<Tgt, ConversionCode> str_to_floating(StringPiece* src) noexcept {
using namespace double_conversion;
static StringToDoubleConverter
conv(StringToDoubleConverter::ALLOW_TRAILING_JUNK
......@@ -366,7 +345,7 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) {
nullptr, nullptr);
if (src->empty()) {
return ConversionResult<Tgt>(ConversionError::EMPTY_INPUT_STRING);
return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
}
int length;
......@@ -383,10 +362,10 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) {
// that was processed, so we need to check if that character was
// whitespace or not.
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);
return ConversionResult<Tgt>(result);
return result;
}
auto* e = src->end();
......@@ -435,7 +414,7 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) {
if (result == 0.0) {
// All bets are off
return ConversionResult<Tgt>(ConversionError::STRING_TO_FLOAT_ERROR);
return makeUnexpected(ConversionCode::STRING_TO_FLOAT_ERROR);
}
if (negative) {
......@@ -444,11 +423,13 @@ ConversionResult<Tgt> str_to_floating(StringPiece* src) {
src->assign(b, e);
return ConversionResult<Tgt>(result);
return result;
}
template ConversionResult<float> str_to_floating<float>(StringPiece* src);
template ConversionResult<double> str_to_floating<double>(StringPiece* src);
template Expected<float, ConversionCode> str_to_floating<float>(
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,
......@@ -460,39 +441,39 @@ class SignedValueHandler;
template <typename T>
class SignedValueHandler<T, true> {
public:
ConversionError::Code init(const char*& b) {
ConversionCode init(const char*& b) {
negative_ = false;
if (!std::isdigit(*b)) {
if (*b == '-') {
negative_ = true;
} else if (UNLIKELY(*b != '+')) {
return ConversionError::INVALID_LEADING_CHAR;
return ConversionCode::INVALID_LEADING_CHAR;
}
++b;
}
return ConversionError::SUCCESS;
return ConversionCode::SUCCESS;
}
ConversionError::Code overflow() {
return negative_ ? ConversionError::NEGATIVE_OVERFLOW
: ConversionError::POSITIVE_OVERFLOW;
ConversionCode overflow() {
return negative_ ? ConversionCode::NEGATIVE_OVERFLOW
: ConversionCode::POSITIVE_OVERFLOW;
}
template <typename U>
ConversionResult<T> finalize(U value) {
Expected<T, ConversionCode> finalize(U value) {
T rv;
if (negative_) {
rv = -value;
if (UNLIKELY(rv > 0)) {
return ConversionResult<T>(ConversionError::NEGATIVE_OVERFLOW);
return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
}
} else {
rv = value;
if (UNLIKELY(rv < 0)) {
return ConversionResult<T>(ConversionError::POSITIVE_OVERFLOW);
return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
}
}
return ConversionResult<T>(rv);
return rv;
}
private:
......@@ -503,16 +484,16 @@ class SignedValueHandler<T, true> {
template <typename T>
class SignedValueHandler<T, false> {
public:
ConversionError::Code init(const char*&) {
return ConversionError::SUCCESS;
ConversionCode init(const char*&) {
return ConversionCode::SUCCESS;
}
ConversionError::Code overflow() {
return ConversionError::POSITIVE_OVERFLOW;
ConversionCode overflow() {
return ConversionCode::POSITIVE_OVERFLOW;
}
ConversionResult<T> finalize(T value) {
return ConversionResult<T>(value);
Expected<T, ConversionCode> finalize(T value) {
return value;
}
};
......@@ -524,15 +505,17 @@ class SignedValueHandler<T, false> {
* an appropriate error.
*/
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;
assert(b <= e);
SignedValueHandler<Tgt> sgn;
auto err = sgn.init(b);
if (UNLIKELY(err != ConversionError::SUCCESS)) {
return ConversionResult<Tgt>(err);
if (UNLIKELY(err != ConversionCode::SUCCESS)) {
return makeUnexpected(err);
}
size_t size = e - b;
......@@ -545,7 +528,7 @@ inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) {
if (b < e && *b == '0') {
for (++b;; ++b) {
if (b == e) {
return ConversionResult<Tgt>(Tgt(0)); // just zeros, e.g. "0000"
return Tgt(0); // just zeros, e.g. "0000"
}
if (*b != '0') {
size = e - b;
......@@ -556,7 +539,7 @@ inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) {
if (size > std::numeric_limits<UT>::digits10 &&
(size != std::numeric_limits<UT>::digits10 + 1 ||
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) {
default:
assert(b == e);
if (size == 0) {
return ConversionResult<Tgt>(ConversionError::NO_DIGITS);
return makeUnexpected(ConversionCode::NO_DIGITS);
}
break;
}
......@@ -619,46 +602,52 @@ inline ConversionResult<Tgt> digits_to(const char* b, const char* const e) {
return sgn.finalize(result);
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 ConversionResult<signed char> digits_to<signed char>(
template Expected<char, ConversionCode> digits_to<char>(
const char*,
const char*);
template ConversionResult<unsigned char> digits_to<unsigned char>(
const char*) noexcept;
template Expected<signed char, ConversionCode> digits_to<signed char>(
const char*,
const char*);
template ConversionResult<short> digits_to<short>(const char*, const char*);
template ConversionResult<unsigned short> digits_to<unsigned short>(
const char*) noexcept;
template Expected<unsigned char, ConversionCode> digits_to<unsigned char>(
const char*,
const char*);
const char*) noexcept;
template ConversionResult<int> digits_to<int>(const char*, const char*);
template ConversionResult<unsigned int> digits_to<unsigned int>(
template Expected<short, ConversionCode> digits_to<short>(
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 ConversionResult<unsigned long> digits_to<unsigned long>(
template Expected<int, ConversionCode> digits_to<int>(
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*);
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*) noexcept;
#if FOLLY_HAVE_INT128_T
template ConversionResult<__int128> digits_to<__int128>(
template Expected<long long, ConversionCode> digits_to<long long>(
const char*,
const char*);
template ConversionResult<unsigned __int128> digits_to<unsigned __int128>(
const char*) noexcept;
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*) noexcept;
template Expected<unsigned __int128, ConversionCode>
digits_to<unsigned __int128>(const char*, const char*) noexcept;
#endif
/**
......@@ -666,14 +655,14 @@ template ConversionResult<unsigned __int128> digits_to<unsigned __int128>(
* StringPiece parameter to munch the already-parsed characters.
*/
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;
auto b = src->data(), past = src->data() + src->size();
for (;; ++b) {
if (UNLIKELY(b >= past)) {
return ConversionResult<Tgt>(ConversionError::EMPTY_INPUT_STRING);
return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
}
if (!std::isspace(*b)) {
break;
......@@ -683,63 +672,93 @@ ConversionResult<Tgt> str_to_integral(StringPiece* src) {
SignedValueHandler<Tgt> sgn;
auto err = sgn.init(b);
if (UNLIKELY(err != ConversionError::SUCCESS)) {
return ConversionResult<Tgt>(err);
if (UNLIKELY(err != ConversionCode::SUCCESS)) {
return makeUnexpected(err);
}
if (std::is_signed<Tgt>::value && UNLIKELY(b >= past)) {
return ConversionResult<Tgt>(ConversionError::NO_DIGITS);
return makeUnexpected(ConversionCode::NO_DIGITS);
}
if (UNLIKELY(!isdigit(*b))) {
return ConversionResult<Tgt>(ConversionError::NON_DIGIT_CHAR);
return makeUnexpected(ConversionCode::NON_DIGIT_CHAR);
}
auto m = findFirstNonDigit(b + 1, past);
auto tmp = digits_to<UT>(b, m);
if (UNLIKELY(!tmp.success())) {
return ConversionResult<Tgt>(
tmp.error == ConversionError::POSITIVE_OVERFLOW ? sgn.overflow()
: tmp.error);
if (UNLIKELY(!tmp.hasValue())) {
return makeUnexpected(
tmp.error() == ConversionCode::POSITIVE_OVERFLOW ? sgn.overflow()
: 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());
}
return res;
}
template ConversionResult<char> str_to_integral<char>(StringPiece* src);
template ConversionResult<signed char> str_to_integral<signed char>(
StringPiece* src);
template ConversionResult<unsigned char> str_to_integral<unsigned char>(
StringPiece* src);
template ConversionResult<short> str_to_integral<short>(StringPiece* src);
template ConversionResult<unsigned short> str_to_integral<unsigned short>(
StringPiece* src);
template ConversionResult<int> str_to_integral<int>(StringPiece* src);
template ConversionResult<unsigned int> str_to_integral<unsigned int>(
StringPiece* src);
template ConversionResult<long> str_to_integral<long>(StringPiece* src);
template ConversionResult<unsigned long> str_to_integral<unsigned long>(
StringPiece* src);
template ConversionResult<long long> str_to_integral<long long>(
StringPiece* src);
template ConversionResult<unsigned long long>
str_to_integral<unsigned long long>(StringPiece* src);
template Expected<char, ConversionCode> str_to_integral<char>(
StringPiece* src) noexcept;
template Expected<signed char, ConversionCode> str_to_integral<signed char>(
StringPiece* src) noexcept;
template Expected<unsigned char, ConversionCode> str_to_integral<unsigned char>(
StringPiece* src) noexcept;
template Expected<short, ConversionCode> str_to_integral<short>(
StringPiece* src) noexcept;
template Expected<unsigned short, ConversionCode>
str_to_integral<unsigned short>(StringPiece* src) noexcept;
template Expected<int, ConversionCode> str_to_integral<int>(
StringPiece* src) noexcept;
template Expected<unsigned int, ConversionCode> str_to_integral<unsigned int>(
StringPiece* src) noexcept;
template Expected<long, ConversionCode> str_to_integral<long>(
StringPiece* src) noexcept;
template Expected<unsigned long, ConversionCode> str_to_integral<unsigned long>(
StringPiece* src) noexcept;
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
template ConversionResult<__int128> str_to_integral<__int128>(StringPiece* src);
template ConversionResult<unsigned __int128> str_to_integral<unsigned __int128>(
StringPiece* src);
template Expected<__int128, ConversionCode> str_to_integral<__int128>(
StringPiece* src) noexcept;
template Expected<unsigned __int128, ConversionCode>
str_to_integral<unsigned __int128>(StringPiece* src) noexcept;
#endif
} // 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
......@@ -39,17 +39,17 @@
#include <double-conversion/double-conversion.h> // V8 JavaScript implementation
#include <folly/Demangle.h>
#include <folly/Expected.h>
#include <folly/FBString.h>
#include <folly/Likely.h>
#include <folly/Range.h>
#include <folly/Unit.h>
#include <folly/portability/Math.h>
namespace folly {
class ConversionError : public std::range_error {
public:
// Keep this in sync with kErrorStrings in Conv.cpp
enum Code {
// Keep this in sync with kErrorStrings in Conv.cpp
enum class ConversionCode : unsigned char {
SUCCESS,
EMPTY_INPUT_STRING,
NO_DIGITS,
......@@ -65,49 +65,62 @@ class ConversionError : public std::range_error {
ARITH_NEGATIVE_OVERFLOW,
ARITH_LOSS_OF_PRECISION,
NUM_ERROR_CODES, // has to be the last entry
};
};
ConversionError(const std::string& str, Code code)
: std::range_error(str), code_(code) {}
struct ConversionErrorBase : std::range_error {
using std::range_error::range_error;
};
ConversionError(const char* str, Code code)
: std::range_error(str), code_(code) {}
class ConversionError : public ConversionErrorBase {
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:
Code code_;
ConversionCode code_;
};
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());
}
/*******************************************************************************
* 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 {
/**
* 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) {
if (!std::isspace(c)) {
return ConversionError::NON_WHITESPACE_AFTER_END;
if (UNLIKELY(!std::isspace(c))) {
return ConversionCode::NON_WHITESPACE_AFTER_END;
}
}
return ConversionError::SUCCESS;
return ConversionCode::SUCCESS;
}
/**
......@@ -115,42 +128,57 @@ inline ConversionError::Code enforceWhitespaceErr(StringPiece sp) {
*/
inline void enforceWhitespace(StringPiece sp) {
auto err = enforceWhitespaceErr(sp);
if (err != ConversionError::SUCCESS) {
throw detail::makeConversionError(err, sp);
if (err != ConversionCode::SUCCESS) {
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>
struct ConversionResult {
explicit ConversionResult(T v) : value(v) {}
explicit ConversionResult(ConversionError::Code e) : error(e) {}
bool success() const {
return error == ConversionError::SUCCESS;
}
template <class Tgt, class Src>
typename std::enable_if<
std::is_same<Tgt, typename std::decay<Src>::type>::value,
Expected<Tgt, ConversionCode>>::type
tryTo(Src&& value) {
return std::forward<Src>(value);
}
T value;
ConversionError::Code error{ConversionError::SUCCESS};
};
template <class Tgt, class Src>
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.
* to<T>(T) returns itself for all types T.
* 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_same<Tgt, Src>::value, Tgt>::type
to(const Src & value) {
return value;
typename std::enable_if<
std::is_arithmetic<Src>::value && !std::is_same<Tgt, Src>::value &&
std::is_same<Tgt, bool>::value,
Expected<Tgt, ConversionCode>>::type
tryTo(const Src& value) {
return value != Src();
}
template <class Tgt, class Src>
typename std::enable_if<std::is_same<Tgt, Src>::value, Tgt>::type
to(Src && value) {
return std::forward<Src>(value);
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();
}
/*******************************************************************************
......@@ -159,34 +187,28 @@ to(Src && value) {
namespace detail {
template <class T>
const T& getLastElement(const T & v) {
return v;
}
template <typename... Ts>
struct LastElementImpl {
static void call(Ignored<Ts>...) {}
};
template <class T, class... Ts>
typename std::tuple_element<
sizeof...(Ts),
std::tuple<T, Ts...> >::type const&
getLastElement(const T&, const Ts&... vs) {
return getLastElement(vs...);
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>
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>
struct last_element {
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;
struct LastElement : std::decay<decltype(
LastElementImpl<Ts...>::call(std::declval<Ts>()...))> {
};
} // namespace detail
......@@ -373,11 +395,10 @@ toAppend(Src value, Tgt * result) {
}
}
template<class Src>
typename std::enable_if<
std::is_convertible<Src, const char*>::value,
size_t>::type
estimateSpaceNeeded(Src value) {
template <class Src>
typename std::enable_if<std::is_convertible<Src, const char*>::value, size_t>::
type
estimateSpaceNeeded(Src value) {
const char *c = value;
if (c) {
return folly::StringPiece(value).size();
......@@ -747,11 +768,10 @@ toAppendStrImpl(const T& v, Tgt result) {
}
template <class T, class... Ts>
typename std::enable_if<sizeof...(Ts) >= 2
&& IsSomeString<
typename std::remove_pointer<
typename detail::last_element<Ts...>::type
>::type>::value>::type
typename std::enable_if<
sizeof...(Ts) >= 2 &&
IsSomeString<typename std::remove_pointer<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type
toAppendStrImpl(const T& v, const Ts&... vs) {
toAppend(v, getLastElement(vs...));
toAppendStrImpl(vs...);
......@@ -765,11 +785,10 @@ toAppendDelimStrImpl(const Delimiter& /* delim */, const T& v, Tgt result) {
}
template <class Delimiter, class T, class... Ts>
typename std::enable_if<sizeof...(Ts) >= 2
&& IsSomeString<
typename std::remove_pointer<
typename detail::last_element<Ts...>::type
>::type>::value>::type
typename std::enable_if<
sizeof...(Ts) >= 2 &&
IsSomeString<typename std::remove_pointer<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type
toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) {
// 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
......@@ -803,11 +822,10 @@ toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) {
* }
*/
template <class... Ts>
typename std::enable_if<sizeof...(Ts) >= 3
&& IsSomeString<
typename std::remove_pointer<
typename detail::last_element<Ts...>::type
>::type>::value>::type
typename std::enable_if<
sizeof...(Ts) >= 3 &&
IsSomeString<typename std::remove_pointer<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type
toAppend(const Ts&... vs) {
::folly::detail::toAppendStrImpl(vs...);
}
......@@ -833,11 +851,8 @@ void toAppend(const pid_t a, Tgt* res) {
* will probably save a few calls to malloc.
*/
template <class... Ts>
typename std::enable_if<
IsSomeString<
typename std::remove_pointer<
typename detail::last_element<Ts...>::type
>::type>::value>::type
typename std::enable_if<IsSomeString<typename std::remove_pointer<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type
toAppendFit(const Ts&... vs) {
::folly::detail::reserveInTarget(vs...);
toAppend(vs...);
......@@ -874,11 +889,10 @@ typename std::enable_if<IsSomeString<Tgt>::value>::type toAppendDelim(
* comments for toAppend for details about memory allocation.
*/
template <class Delimiter, class... Ts>
typename std::enable_if<sizeof...(Ts) >= 3
&& IsSomeString<
typename std::remove_pointer<
typename detail::last_element<Ts...>::type
>::type>::value>::type
typename std::enable_if<
sizeof...(Ts) >= 3 &&
IsSomeString<typename std::remove_pointer<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type
toAppendDelim(const Delimiter& delim, const Ts&... vs) {
detail::toAppendDelimStrImpl(delim, vs...);
}
......@@ -887,11 +901,8 @@ toAppendDelim(const Delimiter& delim, const Ts&... vs) {
* Detail in comment for toAppendFit
*/
template <class Delimiter, class... Ts>
typename std::enable_if<
IsSomeString<
typename std::remove_pointer<
typename detail::last_element<Ts...>::type
>::type>::value>::type
typename std::enable_if<IsSomeString<typename std::remove_pointer<
typename detail::LastElement<const Ts&...>::type>::type>::value>::type
toAppendDelimFit(const Delimiter& delim, const Ts&... vs) {
detail::reserveInTargetDelim(delim, vs...);
toAppendDelim(delim, vs...);
......@@ -906,9 +917,10 @@ void toAppendDelimFit(const De&, const Ts&) {}
*/
template <class Tgt, class... Ts>
typename std::enable_if<
IsSomeString<Tgt>::value && (
sizeof...(Ts) != 1 ||
!std::is_same<Tgt, typename detail::last_element<Ts...>::type>::value),
IsSomeString<Tgt>::value &&
(sizeof...(Ts) != 1 ||
!std::is_same<Tgt, typename detail::LastElement<const Ts&...>::type>::
value),
Tgt>::type
to(const Ts&... vs) {
Tgt result;
......@@ -933,9 +945,10 @@ toDelim(const Delim& /* delim */, const Src& value) {
*/
template <class Tgt, class Delim, class... Ts>
typename std::enable_if<
IsSomeString<Tgt>::value && (
sizeof...(Ts) != 1 ||
!std::is_same<Tgt, typename detail::last_element<Ts...>::type>::value),
IsSomeString<Tgt>::value &&
(sizeof...(Ts) != 1 ||
!std::is_same<Tgt, typename detail::LastElement<const Ts&...>::type>::
value),
Tgt>::type
toDelim(const Delim& delim, const Ts&... vs) {
Tgt result;
......@@ -949,130 +962,121 @@ toDelim(const Delim& delim, const Ts&... vs) {
namespace detail {
ConversionResult<bool> str_to_bool(StringPiece* src);
Expected<bool, ConversionCode> str_to_bool(StringPiece* src) noexcept;
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>(
StringPiece* src);
extern template ConversionResult<double> str_to_floating<double>(
StringPiece* src);
extern template Expected<float, ConversionCode> str_to_floating<float>(
StringPiece* src) noexcept;
extern template Expected<double, ConversionCode> str_to_floating<double>(
StringPiece* src) noexcept;
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>(
const char*,
const char*);
extern template ConversionResult<signed char> digits_to<signed char>(
extern template Expected<char, ConversionCode> digits_to<char>(
const char*,
const char*);
extern template ConversionResult<unsigned char> digits_to<unsigned char>(
const char*) noexcept;
extern template Expected<signed char, ConversionCode> digits_to<signed 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*);
extern template ConversionResult<unsigned short> digits_to<unsigned short>(
const char*,
const char*);
const char*) noexcept;
extern template Expected<unsigned short, ConversionCode>
digits_to<unsigned short>(const char*, const char*) noexcept;
extern template ConversionResult<int> digits_to<int>(const char*, const char*);
extern template ConversionResult<unsigned int> digits_to<unsigned int>(
extern template Expected<int, ConversionCode> digits_to<int>(
const char*,
const char*);
extern template ConversionResult<long> digits_to<long>(
const char*) noexcept;
extern template Expected<unsigned int, ConversionCode> digits_to<unsigned int>(
const char*,
const char*);
extern template ConversionResult<unsigned long> digits_to<unsigned long>(
const char*) noexcept;
extern template Expected<long, ConversionCode> digits_to<long>(
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*);
extern template ConversionResult<unsigned long long>
digits_to<unsigned long long>(const char*, const char*);
const char*) noexcept;
extern template Expected<unsigned long long, ConversionCode>
digits_to<unsigned long long>(const char*, const char*) noexcept;
#if FOLLY_HAVE_INT128_T
extern template ConversionResult<__int128> digits_to<__int128>(
extern template Expected<__int128, ConversionCode> digits_to<__int128>(
const char*,
const char*);
extern template ConversionResult<unsigned __int128>
digits_to<unsigned __int128>(const char*, const char*);
const char*) noexcept;
extern template Expected<unsigned __int128, ConversionCode>
digits_to<unsigned __int128>(const char*, const char*) noexcept;
#endif
template <class T>
ConversionResult<T> str_to_integral(StringPiece* src);
extern template ConversionResult<char> str_to_integral<char>(StringPiece* src);
extern template ConversionResult<signed char> str_to_integral<signed char>(
StringPiece* src);
extern template ConversionResult<unsigned char> str_to_integral<unsigned char>(
StringPiece* src);
extern template ConversionResult<short> str_to_integral<short>(
StringPiece* src);
extern template ConversionResult<unsigned short>
str_to_integral<unsigned short>(StringPiece* src);
extern template ConversionResult<int> str_to_integral<int>(StringPiece* src);
extern template ConversionResult<unsigned int> str_to_integral<unsigned int>(
StringPiece* src);
extern template ConversionResult<long> str_to_integral<long>(StringPiece* src);
extern template ConversionResult<unsigned long> str_to_integral<unsigned long>(
StringPiece* src);
extern template ConversionResult<long long> str_to_integral<long long>(
StringPiece* src);
extern template ConversionResult<unsigned long long>
str_to_integral<unsigned long long>(StringPiece* src);
Expected<T, ConversionCode> str_to_integral(StringPiece* src) noexcept;
extern template Expected<char, ConversionCode> str_to_integral<char>(
StringPiece* src) noexcept;
extern template Expected<signed char, ConversionCode>
str_to_integral<signed char>(StringPiece* src) noexcept;
extern template Expected<unsigned char, ConversionCode>
str_to_integral<unsigned char>(StringPiece* src) noexcept;
extern template Expected<short, ConversionCode> str_to_integral<short>(
StringPiece* src) noexcept;
extern template Expected<unsigned short, ConversionCode>
str_to_integral<unsigned short>(StringPiece* src) noexcept;
extern template Expected<int, ConversionCode> str_to_integral<int>(
StringPiece* src) noexcept;
extern template Expected<unsigned int, ConversionCode>
str_to_integral<unsigned int>(StringPiece* src) noexcept;
extern template Expected<long, ConversionCode> str_to_integral<long>(
StringPiece* src) noexcept;
extern template Expected<unsigned long, ConversionCode>
str_to_integral<unsigned long>(StringPiece* src) noexcept;
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
extern template ConversionResult<__int128> str_to_integral<__int128>(
StringPiece* src);
extern template ConversionResult<unsigned __int128>
str_to_integral<unsigned __int128>(StringPiece* src);
extern template Expected<__int128, ConversionCode> str_to_integral<__int128>(
StringPiece* src) noexcept;
extern template Expected<unsigned __int128, ConversionCode>
str_to_integral<unsigned __int128>(StringPiece* src) noexcept;
#endif
template <typename T>
typename std::enable_if<std::is_same<T, bool>::value, ConversionResult<T>>::type
convertTo(StringPiece* src) {
typename std::
enable_if<std::is_same<T, bool>::value, Expected<T, ConversionCode>>::type
convertTo(StringPiece* src) noexcept {
return str_to_bool(src);
}
template <typename T>
typename std::enable_if<
std::is_floating_point<T>::value, ConversionResult<T>>::type
convertTo(StringPiece* src) {
std::is_floating_point<T>::value,
Expected<T, ConversionCode>>::type
convertTo(StringPiece* src) noexcept {
return str_to_floating<T>(src);
}
template <typename T>
typename std::enable_if<
std::is_integral<T>::value && !std::is_same<T, bool>::value,
ConversionResult<T>>::type
convertTo(StringPiece* src) {
Expected<T, ConversionCode>>::type
convertTo(StringPiece* src) noexcept {
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
/**
......@@ -1081,12 +1085,22 @@ inline wrap(ConversionResult<typename WrapperInfo<T>::type> res, Gen&& gen) {
*/
template <typename Tgt>
typename std::enable_if<
std::is_integral<typename detail::WrapperInfo<Tgt>::type>::value &&
!std::is_same<typename detail::WrapperInfo<Tgt>::type, bool>::value,
std::is_integral<Tgt>::value && !std::is_same<Tgt, 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
to(const char* b, const char* e) {
auto res = detail::digits_to<typename detail::WrapperInfo<Tgt>::type>(b, e);
return detail::wrap<Tgt>(res, [&] { return StringPiece(b, e); });
return tryTo<Tgt>(b, e).thenOrThrow(
[](Tgt res) { return res; },
[=](ConversionCode code) {
return makeConversionError(code, StringPiece(b, e));
});
}
/*******************************************************************************
......@@ -1094,47 +1108,21 @@ to(const char* b, const char* e) {
******************************************************************************/
/**
* 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.
* Parsing strings to numeric types.
*/
template <typename Tgt>
typename std::enable_if<
std::is_arithmetic<typename detail::WrapperInfo<Tgt>::type>::value>::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
FOLLY_WARN_UNUSED_RESULT inline typename std::enable_if<
std::is_arithmetic<Tgt>::value,
Expected<StringPiece, ConversionCode>>::type
parseTo(StringPiece src, Tgt& out) {
auto res = detail::convertTo<typename detail::WrapperInfo<Tgt>::type>(&src);
if (LIKELY(res.success())) {
res.error = detail::enforceWhitespaceErr(src);
}
out = detail::wrap<Tgt>(res, [&] { return src; });
return detail::convertTo<Tgt>(&src).then(
[&](Tgt res) { return void(out = res), src; });
}
/*******************************************************************************
* 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 {
/**
......@@ -1147,22 +1135,22 @@ typename std::enable_if<
std::is_integral<Src>::value && !std::is_same<Tgt, Src>::value &&
!std::is_same<Tgt, bool>::value &&
std::is_integral<Tgt>::value,
ConversionResult<Tgt>>::type
convertTo(const Src& value) {
Expected<Tgt, ConversionCode>>::type
convertTo(const Src& value) noexcept {
/* static */ if (
std::numeric_limits<Tgt>::max() < std::numeric_limits<Src>::max()) {
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 (
std::is_signed<Src>::value &&
(!std::is_signed<Tgt>::value || sizeof(Src) > sizeof(Tgt))) {
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>
typename std::enable_if<
std::is_floating_point<Tgt>::value && std::is_floating_point<Src>::value &&
!std::is_same<Tgt, Src>::value,
ConversionResult<Tgt>>::type
convertTo(const Src& value) {
Expected<Tgt, ConversionCode>>::type
convertTo(const Src& value) noexcept {
/* static */ if (
std::numeric_limits<Tgt>::max() < std::numeric_limits<Src>::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()) {
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>
typename std::enable_if<
(std::is_integral<Src>::value && std::is_floating_point<Tgt>::value) ||
(std::is_floating_point<Src>::value && std::is_integral<Tgt>::value),
ConversionResult<Tgt>>::type
convertTo(const Src& value) {
Expected<Tgt, ConversionCode>>::type
convertTo(const Src& value) noexcept {
if (LIKELY(checkConversion<Tgt>(value))) {
Tgt result = static_cast<Tgt>(value);
if (LIKELY(checkConversion<Src>(result))) {
Src witness = static_cast<Src>(result);
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>
......@@ -1286,12 +1274,19 @@ using IsArithToArith = std::integral_constant<
template <typename Tgt, typename Src>
typename std::enable_if<
detail::IsArithToArith<
typename detail::WrapperInfo<Tgt>::type, Src>::value, Tgt>::type
to(const Src& 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);
detail::IsArithToArith<Tgt, Src>::value,
Expected<Tgt, ConversionCode>>::type
tryTo(const Src& value) noexcept {
return detail::convertTo<Tgt>(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) {
* argument-dependent lookup:
*
* namespace other_namespace {
* void parseTo(::folly::StringPiece, OtherType&);
* ::folly::Expected<::folly::StringPiece, SomeErrorCode>
* parseTo(::folly::StringPiece, OtherType&) noexcept;
* }
******************************************************************************/
template <class T>
typename std::enable_if<std::is_enum<T>::value>::type
parseTo(StringPiece in, T& out) {
typename std::underlying_type<T>::type tmp;
parseTo(in, tmp);
out = static_cast<T>(tmp);
}
inline void parseTo(StringPiece in, StringPiece& out) {
FOLLY_WARN_UNUSED_RESULT typename std::enable_if<
std::is_enum<T>::value,
Expected<StringPiece, ConversionCode>>::type
parseTo(StringPiece in, T& out) noexcept {
typename std::underlying_type<T>::type 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()};
}
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.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.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
* whitespace, but no non-space trailing characters.
*/
template <class Tgt>
typename std::enable_if<!std::is_same<StringPiece, Tgt>::value, Tgt>::type
to(StringPiece src) {
inline typename std::enable_if<
!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;
parseTo(src, result);
return result;
return parseTo(*src, result).then([&, src](StringPiece sp) -> Tgt {
*src = sp;
return std::move(result);
});
}
template <class Tgt>
Tgt to(StringPiece* src) {
Tgt result;
parseTo(src, result);
return result;
using Error = detail::ParseToError<Tgt>;
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) {
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) {
std::is_enum<Src>::value && !std::is_same<Src, Tgt>::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));
}
......
......@@ -92,6 +92,15 @@ constexpr bool kHasUnalignedAccess = false;
# define FOLLY_ALWAYS_INLINE inline
#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
#ifdef _MSC_VER
# define FOLLY_TARGET_ATTRIBUTE(target)
......
......@@ -328,7 +328,7 @@ bool splitFixed(const Delim& delimiter, StringPiece input, OutputType& output) {
if (exact && UNLIKELY(std::string::npos != input.find(delimiter))) {
return false;
}
parseTo(input, output);
output = folly::to<OutputType>(input);
return true;
}
......@@ -346,7 +346,7 @@ bool splitFixed(
StringPiece tail(input.begin() + cut + detail::delimSize(delimiter),
input.end());
if (LIKELY(splitFixed<exact>(delimiter, tail, outTail...))) {
parseTo(head, outHead);
outHead = folly::to<OutputType>(head);
return true;
}
return false;
......
......@@ -491,7 +491,8 @@ struct IsConvertible {
template <class T>
struct IsConvertible<
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 };
};
......
......@@ -215,3 +215,31 @@ is returned, which can be tested for as follows:
// 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) {
}
}
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>
inline void ptrPairToIntClassic(StringPiece sp, uint32_t n) {
for (uint32_t i = 0; i < n; ++i) {
......@@ -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;
template <typename T, typename U>
......@@ -944,6 +964,24 @@ inline size_t arithToArithClassic(const U* in, uint32_t 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 folly {
......@@ -980,6 +1018,12 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}};
} \
BENCHMARK(stringTo##name##ClassicError, 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) \
......@@ -988,6 +1032,12 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}};
} \
BENCHMARK(ptrPairTo##name##ClassicError, 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) \
......@@ -996,6 +1046,12 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}};
} \
BENCHMARK_MULTI(name##ClassicError) { \
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) \
......
......@@ -18,6 +18,7 @@
#include <folly/Conv.h>
#include <folly/Foreach.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <limits>
#include <sstream>
#include <stdexcept>
......@@ -820,6 +821,14 @@ TEST(Conv, StringToBool) {
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) {
EXPECT_EQ(to<int>(42.0f), 42);
EXPECT_EQ(to<int8_t>(-128.0f), int8_t(-128));
......@@ -874,7 +883,7 @@ template <typename F>
void testConvError(
F&& expr,
const char* exprStr,
ConversionError::Code code,
ConversionCode code,
const char* value,
bool quotedValue,
int line) {
......@@ -907,7 +916,7 @@ void testConvError(
testConvError( \
[&] { return expr; }, \
#expr, \
ConversionError::code, \
ConversionCode::code, \
value, \
quoted, \
__LINE__)
......@@ -1026,6 +1035,91 @@ TEST(Conv, ConversionErrorFloatToInt) {
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) {
char buf[21];
......@@ -1091,10 +1185,12 @@ struct Dimensions {
}
};
void parseTo(folly::StringPiece in, Dimensions& out) {
out.w = folly::to<int>(&in);
in.removePrefix("x");
out.h = folly::to<int>(&in);
Expected<StringPiece, ConversionCode> parseTo(
folly::StringPiece in,
Dimensions& out) {
return parseTo(in, out.w)
.then([](StringPiece sp) { return sp.removePrefix("x"), sp; })
.then([&](StringPiece sp) { return parseTo(sp, out.h); });
}
template <class String>
......@@ -1117,3 +1213,10 @@ TEST(Conv, custom_kkproviders) {
EXPECT_GT(str.capacity(), 2000);
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 {
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") {
out = Color::Red;
} else if (in == "B") {
out = Color::Blue;
} else {
throw runtime_error("");
return makeUnexpected(ColorErrorCode::INVALID_COLOR);
}
return StringPiece(in.end(), in.end());
}
}
......@@ -938,6 +951,8 @@ TEST(Split, fixed_convert_custom) {
EXPECT_TRUE(folly::split(',', "R,B", c1, c2));
EXPECT_EQ(c1, my::Color::Red);
EXPECT_EQ(c2, my::Color::Blue);
EXPECT_THROW(folly::split(',', "B,G", c1, c2), my::ColorError);
}
TEST(String, join) {
......
......@@ -52,7 +52,7 @@ TEST(Traits, scalars) {
TEST(Traits, containers) {
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));
}
......
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