From: Eric Niebler Date: Mon, 15 Aug 2016 21:03:58 +0000 (-0700) Subject: Use folly::Expected to implement folly::tryTo, a non-throwing variant of folly::to X-Git-Tag: v2016.08.22.00~37 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=89cc32931877aa684e1a1f06776f564a49d39eab;p=folly.git 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: tryTo, which returns an Expected. Here is how the non-throwing interface compares to the regular interface in terms of performance. On the successful path, there's generally not much difference between using the throwing and non-throwing interfaces. For the error path, tryTo<> is about three orders of magnitude faster than to<>. Reviewed By: mhx Differential Revision: D3557832 fbshipit-source-id: 25b29834b6fda474a7cdd43a7dea69ff4ee183c8 --- diff --git a/folly/Conv.cpp b/folly/Conv.cpp index e11d8693..0ab6dcf5 100644 --- a/folly/Conv.cpp +++ b/folly/Conv.cpp @@ -207,8 +207,10 @@ struct ErrorString { bool quote; }; -// Keep this in sync with ConversionError::Code in Conv.h -constexpr const std::array +// Keep this in sync with ConversionCode in Conv.h +constexpr const std::array< + ErrorString, + static_cast(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 str_to_bool(StringPiece* src) { +Expected str_to_bool(StringPiece* src) noexcept { auto b = src->begin(), e = src->end(); for (;; ++b) { if (b >= e) { - return ConversionResult(ConversionError::EMPTY_INPUT_STRING); + return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); } if (!std::isspace(*b)) { break; @@ -297,7 +276,7 @@ ConversionResult str_to_bool(StringPiece* src) { result = false; for (; b < e && isdigit(*b); ++b) { if (result || (*b != '0' && *b != '1')) { - return ConversionResult(ConversionError::BOOL_OVERFLOW); + return makeUnexpected(ConversionCode::BOOL_OVERFLOW); } result = (*b == '1'); } @@ -338,16 +317,16 @@ ConversionResult str_to_bool(StringPiece* src) { } else if (bool_str_cmp(&b, len, "off")) { result = false; } else { - return ConversionResult(ConversionError::BOOL_INVALID_VALUE); + return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE); } break; default: - return ConversionResult(ConversionError::BOOL_INVALID_VALUE); + return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE); } src->assign(b, e); - return ConversionResult(result); + return result; } /** @@ -355,7 +334,7 @@ ConversionResult str_to_bool(StringPiece* src) { * StringPiece parameter to munch the already-parsed characters. */ template -ConversionResult str_to_floating(StringPiece* src) { +Expected str_to_floating(StringPiece* src) noexcept { using namespace double_conversion; static StringToDoubleConverter conv(StringToDoubleConverter::ALLOW_TRAILING_JUNK @@ -366,7 +345,7 @@ ConversionResult str_to_floating(StringPiece* src) { nullptr, nullptr); if (src->empty()) { - return ConversionResult(ConversionError::EMPTY_INPUT_STRING); + return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); } int length; @@ -383,10 +362,10 @@ ConversionResult 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(ConversionError::EMPTY_INPUT_STRING); + return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); } src->advance(length); - return ConversionResult(result); + return result; } auto* e = src->end(); @@ -435,7 +414,7 @@ ConversionResult str_to_floating(StringPiece* src) { if (result == 0.0) { // All bets are off - return ConversionResult(ConversionError::STRING_TO_FLOAT_ERROR); + return makeUnexpected(ConversionCode::STRING_TO_FLOAT_ERROR); } if (negative) { @@ -444,11 +423,13 @@ ConversionResult str_to_floating(StringPiece* src) { src->assign(b, e); - return ConversionResult(result); + return result; } -template ConversionResult str_to_floating(StringPiece* src); -template ConversionResult str_to_floating(StringPiece* src); +template Expected str_to_floating( + StringPiece* src) noexcept; +template Expected str_to_floating( + StringPiece* src) noexcept; /** * This class takes care of additional processing needed for signed values, @@ -460,39 +441,39 @@ class SignedValueHandler; template class SignedValueHandler { 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 - ConversionResult finalize(U value) { + Expected finalize(U value) { T rv; if (negative_) { rv = -value; if (UNLIKELY(rv > 0)) { - return ConversionResult(ConversionError::NEGATIVE_OVERFLOW); + return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW); } } else { rv = value; if (UNLIKELY(rv < 0)) { - return ConversionResult(ConversionError::POSITIVE_OVERFLOW); + return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW); } } - return ConversionResult(rv); + return rv; } private: @@ -503,16 +484,16 @@ class SignedValueHandler { template class SignedValueHandler { 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 finalize(T value) { - return ConversionResult(value); + Expected finalize(T value) { + return value; } }; @@ -524,15 +505,17 @@ class SignedValueHandler { * an appropriate error. */ template -inline ConversionResult digits_to(const char* b, const char* const e) { +inline Expected digits_to( + const char* b, + const char* const e) noexcept { using UT = typename std::make_unsigned::type; assert(b <= e); SignedValueHandler sgn; auto err = sgn.init(b); - if (UNLIKELY(err != ConversionError::SUCCESS)) { - return ConversionResult(err); + if (UNLIKELY(err != ConversionCode::SUCCESS)) { + return makeUnexpected(err); } size_t size = e - b; @@ -545,7 +528,7 @@ inline ConversionResult digits_to(const char* b, const char* const e) { if (b < e && *b == '0') { for (++b;; ++b) { if (b == e) { - return ConversionResult(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 digits_to(const char* b, const char* const e) { if (size > std::numeric_limits::digits10 && (size != std::numeric_limits::digits10 + 1 || strncmp(b, MaxString::value, size) > 0)) { - return ConversionResult(sgn.overflow()); + return makeUnexpected(sgn.overflow()); } } @@ -611,7 +594,7 @@ inline ConversionResult digits_to(const char* b, const char* const e) { default: assert(b == e); if (size == 0) { - return ConversionResult(ConversionError::NO_DIGITS); + return makeUnexpected(ConversionCode::NO_DIGITS); } break; } @@ -619,46 +602,52 @@ inline ConversionResult digits_to(const char* b, const char* const e) { return sgn.finalize(result); outOfRange: - return ConversionResult(ConversionError::NON_DIGIT_CHAR); + return makeUnexpected(ConversionCode::NON_DIGIT_CHAR); } -template ConversionResult digits_to(const char*, const char*); -template ConversionResult digits_to( +template Expected digits_to( const char*, - const char*); -template ConversionResult digits_to( + const char*) noexcept; +template Expected digits_to( const char*, - const char*); - -template ConversionResult digits_to(const char*, const char*); -template ConversionResult digits_to( + const char*) noexcept; +template Expected digits_to( const char*, - const char*); + const char*) noexcept; -template ConversionResult digits_to(const char*, const char*); -template ConversionResult digits_to( +template Expected digits_to( const char*, - const char*); + const char*) noexcept; +template Expected digits_to( + const char*, + const char*) noexcept; -template ConversionResult digits_to(const char*, const char*); -template ConversionResult digits_to( +template Expected digits_to( const char*, - const char*); + const char*) noexcept; +template Expected digits_to( + const char*, + const char*) noexcept; -template ConversionResult digits_to( +template Expected digits_to( const char*, const char*); -template ConversionResult digits_to( +template Expected digits_to( const char*, - const char*); + const char*) noexcept; -#if FOLLY_HAVE_INT128_T -template ConversionResult<__int128> digits_to<__int128>( +template Expected digits_to( const char*, - const char*); -template ConversionResult digits_to( + const char*) noexcept; +template Expected +digits_to(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 +digits_to(const char*, const char*) noexcept; #endif /** @@ -666,14 +655,14 @@ template ConversionResult digits_to( * StringPiece parameter to munch the already-parsed characters. */ template -ConversionResult str_to_integral(StringPiece* src) { +Expected str_to_integral(StringPiece* src) noexcept { using UT = typename std::make_unsigned::type; auto b = src->data(), past = src->data() + src->size(); for (;; ++b) { if (UNLIKELY(b >= past)) { - return ConversionResult(ConversionError::EMPTY_INPUT_STRING); + return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); } if (!std::isspace(*b)) { break; @@ -683,63 +672,93 @@ ConversionResult str_to_integral(StringPiece* src) { SignedValueHandler sgn; auto err = sgn.init(b); - if (UNLIKELY(err != ConversionError::SUCCESS)) { - return ConversionResult(err); + if (UNLIKELY(err != ConversionCode::SUCCESS)) { + return makeUnexpected(err); } if (std::is_signed::value && UNLIKELY(b >= past)) { - return ConversionResult(ConversionError::NO_DIGITS); + return makeUnexpected(ConversionCode::NO_DIGITS); } if (UNLIKELY(!isdigit(*b))) { - return ConversionResult(ConversionError::NON_DIGIT_CHAR); + return makeUnexpected(ConversionCode::NON_DIGIT_CHAR); } auto m = findFirstNonDigit(b + 1, past); auto tmp = digits_to(b, m); - if (UNLIKELY(!tmp.success())) { - return ConversionResult( - 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 str_to_integral(StringPiece* src); -template ConversionResult str_to_integral( - StringPiece* src); -template ConversionResult str_to_integral( - StringPiece* src); - -template ConversionResult str_to_integral(StringPiece* src); -template ConversionResult str_to_integral( - StringPiece* src); - -template ConversionResult str_to_integral(StringPiece* src); -template ConversionResult str_to_integral( - StringPiece* src); - -template ConversionResult str_to_integral(StringPiece* src); -template ConversionResult str_to_integral( - StringPiece* src); - -template ConversionResult str_to_integral( - StringPiece* src); -template ConversionResult -str_to_integral(StringPiece* src); +template Expected str_to_integral( + StringPiece* src) noexcept; +template Expected str_to_integral( + StringPiece* src) noexcept; +template Expected str_to_integral( + StringPiece* src) noexcept; + +template Expected str_to_integral( + StringPiece* src) noexcept; +template Expected +str_to_integral(StringPiece* src) noexcept; + +template Expected str_to_integral( + StringPiece* src) noexcept; +template Expected str_to_integral( + StringPiece* src) noexcept; + +template Expected str_to_integral( + StringPiece* src) noexcept; +template Expected str_to_integral( + StringPiece* src) noexcept; + +template Expected str_to_integral( + StringPiece* src) noexcept; +template Expected +str_to_integral(StringPiece* src) noexcept; #if FOLLY_HAVE_INT128_T -template ConversionResult<__int128> str_to_integral<__int128>(StringPiece* src); -template ConversionResult str_to_integral( - StringPiece* src); +template Expected<__int128, ConversionCode> str_to_integral<__int128>( + StringPiece* src) noexcept; +template Expected +str_to_integral(StringPiece* src) noexcept; #endif } // namespace detail + +ConversionError makeConversionError(ConversionCode code, StringPiece input) { + using namespace detail; + static_assert( + std::is_unsigned::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 diff --git a/folly/Conv.h b/folly/Conv.h index df34f2fa..9fc19696 100644 --- a/folly/Conv.h +++ b/folly/Conv.h @@ -39,75 +39,88 @@ #include // V8 JavaScript implementation #include +#include #include #include #include +#include #include namespace folly { -class ConversionError : public std::range_error { - public: - // Keep this in sync with kErrorStrings in Conv.cpp - 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 - }; +// Keep this in sync with kErrorStrings in Conv.cpp +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 +}; - 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) returns itself for all types T. */ -template -struct ConversionResult { - explicit ConversionResult(T v) : value(v) {} - explicit ConversionResult(ConversionError::Code e) : error(e) {} - - bool success() const { - return error == ConversionError::SUCCESS; - } +template +typename std::enable_if< + std::is_same::type>::value, + Expected>::type +tryTo(Src&& value) { + return std::forward(value); +} - T value; - ConversionError::Code error{ConversionError::SUCCESS}; -}; +template +typename std::enable_if< + std::is_same::type>::value, + Tgt>::type +to(Src&& value) { + return std::forward(value); } +/******************************************************************************* + * Arithmetic to boolean + ******************************************************************************/ + /** - * The identity conversion function. - * to(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 -typename std::enable_if::value, Tgt>::type -to(const Src & value) { - return value; +typename std::enable_if< + std::is_arithmetic::value && !std::is_same::value && + std::is_same::value, + Expected>::type +tryTo(const Src& value) { + return value != Src(); } template -typename std::enable_if::value, Tgt>::type -to(Src && value) { - return std::forward(value); +typename std::enable_if< + std::is_arithmetic::value && !std::is_same::value && + std::is_same::value, + Tgt>::type +to(const Src& value) { + return value != Src(); } /******************************************************************************* @@ -159,34 +187,28 @@ to(Src && value) { namespace detail { -template -const T& getLastElement(const T & v) { - return v; -} +template +struct LastElementImpl { + static void call(Ignored...) {} +}; -template -typename std::tuple_element< - sizeof...(Ts), - std::tuple >::type const& - getLastElement(const T&, const Ts&... vs) { - return getLastElement(vs...); +template +struct LastElementImpl { + template + static Last call(Ignored..., Last&& last) { + return std::forward(last); + } +}; + +template +auto getLastElement(const Ts&... ts) + -> decltype(LastElementImpl::call(ts...)) { + return LastElementImpl::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 -struct last_element { - typedef typename std::enable_if< - sizeof...(Ts) >= 1, - typename std::tuple_element< - sizeof...(Ts) - 1, std::tuple - >::type>::type type; -}; - -template <> -struct last_element<> { - typedef void type; +struct LastElement : std::decay::call(std::declval()...))> { }; } // namespace detail @@ -373,11 +395,10 @@ toAppend(Src value, Tgt * result) { } } -template -typename std::enable_if< - std::is_convertible::value, - size_t>::type -estimateSpaceNeeded(Src value) { +template +typename std::enable_if::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 -typename std::enable_if= 2 - && IsSomeString< - typename std::remove_pointer< - typename detail::last_element::type - >::type>::value>::type +typename std::enable_if< + sizeof...(Ts) >= 2 && + IsSomeString::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 -typename std::enable_if= 2 - && IsSomeString< - typename std::remove_pointer< - typename detail::last_element::type - >::type>::value>::type +typename std::enable_if< + sizeof...(Ts) >= 2 && + IsSomeString::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 -typename std::enable_if= 3 - && IsSomeString< - typename std::remove_pointer< - typename detail::last_element::type - >::type>::value>::type +typename std::enable_if< + sizeof...(Ts) >= 3 && + IsSomeString::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 -typename std::enable_if< - IsSomeString< - typename std::remove_pointer< - typename detail::last_element::type - >::type>::value>::type +typename std::enable_if::type>::type>::value>::type toAppendFit(const Ts&... vs) { ::folly::detail::reserveInTarget(vs...); toAppend(vs...); @@ -874,11 +889,10 @@ typename std::enable_if::value>::type toAppendDelim( * comments for toAppend for details about memory allocation. */ template -typename std::enable_if= 3 - && IsSomeString< - typename std::remove_pointer< - typename detail::last_element::type - >::type>::value>::type +typename std::enable_if< + sizeof...(Ts) >= 3 && + IsSomeString::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 -typename std::enable_if< - IsSomeString< - typename std::remove_pointer< - typename detail::last_element::type - >::type>::value>::type +typename std::enable_if::type>::type>::value>::type toAppendDelimFit(const Delimiter& delim, const Ts&... vs) { detail::reserveInTargetDelim(delim, vs...); toAppendDelim(delim, vs...); @@ -906,10 +917,11 @@ void toAppendDelimFit(const De&, const Ts&) {} */ template typename std::enable_if< - IsSomeString::value && ( - sizeof...(Ts) != 1 || - !std::is_same::type>::value), - Tgt>::type + IsSomeString::value && + (sizeof...(Ts) != 1 || + !std::is_same::type>:: + value), + Tgt>::type to(const Ts&... vs) { Tgt result; toAppendFit(vs..., &result); @@ -933,10 +945,11 @@ toDelim(const Delim& /* delim */, const Src& value) { */ template typename std::enable_if< - IsSomeString::value && ( - sizeof...(Ts) != 1 || - !std::is_same::type>::value), - Tgt>::type + IsSomeString::value && + (sizeof...(Ts) != 1 || + !std::is_same::type>:: + value), + Tgt>::type toDelim(const Delim& delim, const Ts&... vs) { Tgt result; toAppendDelimFit(delim, vs..., &result); @@ -949,130 +962,121 @@ toDelim(const Delim& delim, const Ts&... vs) { namespace detail { -ConversionResult str_to_bool(StringPiece* src); +Expected str_to_bool(StringPiece* src) noexcept; template -ConversionResult str_to_floating(StringPiece* src); +Expected str_to_floating(StringPiece* src) noexcept; -extern template ConversionResult str_to_floating( - StringPiece* src); -extern template ConversionResult str_to_floating( - StringPiece* src); +extern template Expected str_to_floating( + StringPiece* src) noexcept; +extern template Expected str_to_floating( + StringPiece* src) noexcept; template -ConversionResult digits_to(const char* b, const char* e); +Expected digits_to(const char* b, const char* e) noexcept; -extern template ConversionResult digits_to( - const char*, - const char*); -extern template ConversionResult digits_to( +extern template Expected digits_to( const char*, - const char*); -extern template ConversionResult digits_to( + const char*) noexcept; +extern template Expected digits_to( const char*, - const char*); + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; -extern template ConversionResult digits_to( +extern template Expected digits_to( const char*, - const char*); -extern template ConversionResult digits_to( - const char*, - const char*); + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; -extern template ConversionResult digits_to(const char*, const char*); -extern template ConversionResult digits_to( +extern template Expected digits_to( const char*, - const char*); - -extern template ConversionResult digits_to( + const char*) noexcept; +extern template Expected digits_to( const char*, - const char*); -extern template ConversionResult digits_to( + const char*) noexcept; + +extern template Expected digits_to( const char*, - const char*); + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; -extern template ConversionResult digits_to( +extern template Expected digits_to( const char*, - const char*); -extern template ConversionResult -digits_to(const char*, const char*); + const char*) noexcept; +extern template Expected +digits_to(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 -digits_to(const char*, const char*); + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; #endif template -ConversionResult str_to_integral(StringPiece* src); - -extern template ConversionResult str_to_integral(StringPiece* src); -extern template ConversionResult str_to_integral( - StringPiece* src); -extern template ConversionResult str_to_integral( - StringPiece* src); - -extern template ConversionResult str_to_integral( - StringPiece* src); -extern template ConversionResult -str_to_integral(StringPiece* src); - -extern template ConversionResult str_to_integral(StringPiece* src); -extern template ConversionResult str_to_integral( - StringPiece* src); - -extern template ConversionResult str_to_integral(StringPiece* src); -extern template ConversionResult str_to_integral( - StringPiece* src); - -extern template ConversionResult str_to_integral( - StringPiece* src); -extern template ConversionResult -str_to_integral(StringPiece* src); +Expected str_to_integral(StringPiece* src) noexcept; + +extern template Expected str_to_integral( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; + +extern template Expected str_to_integral( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; + +extern template Expected str_to_integral( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; + +extern template Expected str_to_integral( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; + +extern template Expected str_to_integral( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; #if FOLLY_HAVE_INT128_T -extern template ConversionResult<__int128> str_to_integral<__int128>( - StringPiece* src); -extern template ConversionResult -str_to_integral(StringPiece* src); +extern template Expected<__int128, ConversionCode> str_to_integral<__int128>( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; #endif template -typename std::enable_if::value, ConversionResult>::type -convertTo(StringPiece* src) { +typename std:: + enable_if::value, Expected>::type + convertTo(StringPiece* src) noexcept { return str_to_bool(src); } template typename std::enable_if< - std::is_floating_point::value, ConversionResult>::type -convertTo(StringPiece* src) { + std::is_floating_point::value, + Expected>::type +convertTo(StringPiece* src) noexcept { return str_to_floating(src); } template typename std::enable_if< std::is_integral::value && !std::is_same::value, - ConversionResult>::type -convertTo(StringPiece* src) { + Expected>::type +convertTo(StringPiece* src) noexcept { return str_to_integral(src); } -template -struct WrapperInfo { using type = T; }; - -template -typename std::enable_if< - std::is_same::type, T>::value, T>::type -inline wrap(ConversionResult::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::type> res, Gen&& gen) { */ template typename std::enable_if< - std::is_integral::type>::value && - !std::is_same::type, bool>::value, + std::is_integral::value && !std::is_same::value, + Expected>::type +tryTo(const char* b, const char* e) { + return detail::digits_to(b, e); +} + +template +typename std::enable_if< + std::is_integral::value && !std::is_same::value, Tgt>::type to(const char* b, const char* e) { - auto res = detail::digits_to::type>(b, e); - return detail::wrap(res, [&] { return StringPiece(b, e); }); + return tryTo(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 std::enable_if< - std::is_arithmetic::type>::value>::type -parseTo(StringPiece* src, Tgt& out) { - auto res = detail::convertTo::type>(src); - out = detail::wrap(res, [&] { return *src; }); -} - -template -typename std::enable_if< - std::is_arithmetic::type>::value>::type +FOLLY_WARN_UNUSED_RESULT inline typename std::enable_if< + std::is_arithmetic::value, + Expected>::type parseTo(StringPiece src, Tgt& out) { - auto res = detail::convertTo::type>(&src); - if (LIKELY(res.success())) { - res.error = detail::enforceWhitespaceErr(src); - } - out = detail::wrap(res, [&] { return src; }); + return detail::convertTo(&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 -typename std::enable_if< - std::is_arithmetic::value && !std::is_same::value && - std::is_same::value, - Tgt>::type -to(const Src& value) { - return value != Src(); -} - namespace detail { /** @@ -1147,22 +1135,22 @@ typename std::enable_if< std::is_integral::value && !std::is_same::value && !std::is_same::value && std::is_integral::value, - ConversionResult>::type -convertTo(const Src& value) { + Expected>::type +convertTo(const Src& value) noexcept { /* static */ if ( std::numeric_limits::max() < std::numeric_limits::max()) { if (greater_than::max()>(value)) { - return ConversionResult(ConversionError::ARITH_POSITIVE_OVERFLOW); + return makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW); } } /* static */ if ( std::is_signed::value && (!std::is_signed::value || sizeof(Src) > sizeof(Tgt))) { if (less_than::min()>(value)) { - return ConversionResult(ConversionError::ARITH_NEGATIVE_OVERFLOW); + return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW); } } - return ConversionResult(static_cast(value)); + return static_cast(value); } /** @@ -1174,18 +1162,18 @@ template typename std::enable_if< std::is_floating_point::value && std::is_floating_point::value && !std::is_same::value, - ConversionResult>::type -convertTo(const Src& value) { + Expected>::type +convertTo(const Src& value) noexcept { /* static */ if ( std::numeric_limits::max() < std::numeric_limits::max()) { if (value > std::numeric_limits::max()) { - return ConversionResult(ConversionError::ARITH_POSITIVE_OVERFLOW); + return makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW); } if (value < std::numeric_limits::lowest()) { - return ConversionResult(ConversionError::ARITH_NEGATIVE_OVERFLOW); + return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW); } } - return ConversionResult(boost::implicit_cast(value)); + return boost::implicit_cast(value); } /** @@ -1252,18 +1240,18 @@ template typename std::enable_if< (std::is_integral::value && std::is_floating_point::value) || (std::is_floating_point::value && std::is_integral::value), - ConversionResult>::type -convertTo(const Src& value) { + Expected>::type +convertTo(const Src& value) noexcept { if (LIKELY(checkConversion(value))) { Tgt result = static_cast(value); if (LIKELY(checkConversion(result))) { Src witness = static_cast(result); if (LIKELY(value == witness)) { - return ConversionResult(result); + return result; } } } - return ConversionResult(ConversionError::ARITH_LOSS_OF_PRECISION); + return makeUnexpected(ConversionCode::ARITH_LOSS_OF_PRECISION); } template @@ -1286,13 +1274,20 @@ using IsArithToArith = std::integral_constant< template typename std::enable_if< - detail::IsArithToArith< - typename detail::WrapperInfo::type, Src>::value, Tgt>::type -to(const Src& value) { - auto res = detail::convertTo::type>(value); - return detail::wrap(res, [&] { - return detail::errorValue::type>(value); - }); + detail::IsArithToArith::value, + Expected>::type +tryTo(const Src& value) noexcept { + return detail::convertTo(value); +} + +template +typename std::enable_if::value, Tgt>::type to( + const Src& value) { + return tryTo(value).thenOrThrow( + [](Tgt res) { return res; }, + [&](ConversionCode e) { + return makeConversionError(e, detail::errorValue(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 -typename std::enable_if::value>::type -parseTo(StringPiece in, T& out) { - typename std::underlying_type::type tmp; - parseTo(in, tmp); - out = static_cast(tmp); -} - -inline void parseTo(StringPiece in, StringPiece& out) { +FOLLY_WARN_UNUSED_RESULT typename std::enable_if< + std::is_enum::value, + Expected>::type +parseTo(StringPiece in, T& out) noexcept { + typename std::underlying_type::type tmp{}; + auto restOrError = parseTo(in, tmp); + out = static_cast(tmp); // Harmless if parseTo fails + return restOrError; +} + +FOLLY_WARN_UNUSED_RESULT +inline Expected 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 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 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 +using ParseToResult = decltype(parseTo(StringPiece{}, std::declval())); + +struct CheckTrailingSpace { + Expected operator()(StringPiece sp) const { + auto e = enforceWhitespaceErr(sp); + if (UNLIKELY(e != ConversionCode::SUCCESS)) + return makeUnexpected(e); + return unit; + } +}; + +template +struct ReturnUnit { + template + constexpr Expected operator()(T&&) const { + return unit; + } +}; + +// Older versions of the parseTo customization point threw on error and +// returned void. Handle that. +template +inline typename std::enable_if< + std::is_void>::value, + Expected>::type +parseToWrap(StringPiece sp, Tgt& out) { + parseTo(sp, out); + return StringPiece(sp.end(), sp.end()); +} + +template +inline typename std::enable_if< + !std::is_void>::value, + ParseToResult>::type +parseToWrap(StringPiece sp, Tgt& out) { + return parseTo(sp, out); +} + +template +using ParseToError = ExpectedErrorType()))>; + +} // namespace detail + /** * String or StringPiece to target conversion. Accepts leading and trailing * whitespace, but no non-space trailing characters. */ template -typename std::enable_if::value, Tgt>::type -to(StringPiece src) { +inline typename std::enable_if< + !std::is_same::value, + Expected>>::type +tryTo(StringPiece src) { + Tgt result{}; + using Error = detail::ParseToError; + using Check = typename std::conditional< + std::is_arithmetic::value, + detail::CheckTrailingSpace, + detail::ReturnUnit>::type; + return parseTo(src, result).then(Check(), [&](Unit) { + return std::move(result); + }); +} + +template +inline + typename std::enable_if::value, Tgt>::type + to(StringPiece src) { + Tgt result{}; + using Error = detail::ParseToError; + using Check = typename std::conditional< + std::is_arithmetic::value, + detail::CheckTrailingSpace, + detail::ReturnUnit>::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 +Expected> 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 Tgt to(StringPiece* src) { Tgt result; - parseTo(src, result); - return result; + using Error = detail::ParseToError; + 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 typename std::enable_if< - std::is_enum::value && !std::is_same::value, Tgt>::type -to(const Src & value) { + std::is_enum::value && !std::is_same::value, + Expected>::type +tryTo(const Src& value) { + using I = typename std::underlying_type::type; + return tryTo(static_cast(value)); +} + +template +typename std::enable_if< + std::is_enum::value && !std::is_same::value, + Tgt>::type +tryTo(const Src& value) { + using I = typename std::underlying_type::type; + return tryTo(value).then([](I i) { return static_cast(i); }); +} + +template +typename std::enable_if< + std::is_enum::value && !std::is_same::value, + Tgt>::type +to(const Src& value) { return to(static_cast::type>(value)); } diff --git a/folly/Portability.h b/folly/Portability.h index 56dc7c95..e6d06a4d 100644 --- a/folly/Portability.h +++ b/folly/Portability.h @@ -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) diff --git a/folly/String-inl.h b/folly/String-inl.h index 1d613e9c..2bf0104a 100644 --- a/folly/String-inl.h +++ b/folly/String-inl.h @@ -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(input); return true; } @@ -346,7 +346,7 @@ bool splitFixed( StringPiece tail(input.begin() + cut + detail::delimSize(delimiter), input.end()); if (LIKELY(splitFixed(delimiter, tail, outTail...))) { - parseTo(head, outHead); + outHead = folly::to(head); return true; } return false; diff --git a/folly/String.h b/folly/String.h index fc552f11..5d631340 100644 --- a/folly/String.h +++ b/folly/String.h @@ -491,7 +491,8 @@ struct IsConvertible { template struct IsConvertible< T, - decltype(parseTo(std::declval(), std::declval()))> { + decltype(static_cast( + parseTo(std::declval(), std::declval())))> { enum { value = true }; }; diff --git a/folly/docs/Conv.md b/folly/docs/Conv.md index 8d285e9d..7156c116 100644 --- a/folly/docs/Conv.md +++ b/folly/docs/Conv.md @@ -215,3 +215,31 @@ is returned, which can be tested for as follows: // string could not be parsed } ``` + +#### Non-throwing interfaces + +`tryTo` is the non-throwing variant of `to`. It returns +an `Expected`. You can think of `Expected` +as like an `Optional`, but if the conversion failed, `Expected` +stores an error code instead of a `T`. + +`tryTo` has similar performance as `to` when the +conversion is successful. On the error path, you can expect +`tryTo` to be roughly three orders of magnitude faster than +the throwing `to` and to completely avoid any lock contention +arising from stack unwinding. + +Here is how to use non-throwing conversions: + +``` Cpp + auto t1 = tryTo(str); + if (t1.hasValue()) { + use(t1.value()); + } +``` + +`Expected` has a composability feature to make the above pattern simpler. + +``` Cpp + tryTo(str).then([](int i) { use(i); }); +``` diff --git a/folly/test/ConvBenchmark.cpp b/folly/test/ConvBenchmark.cpp index 0464822b..ec975f3e 100644 --- a/folly/test/ConvBenchmark.cpp +++ b/folly/test/ConvBenchmark.cpp @@ -911,6 +911,16 @@ inline void stringToTypeClassic(const char* str, uint32_t n) { } } +template +inline void stringToTypeOptional(const char* str, uint32_t n) { + for (uint32_t i = 0; i < n; ++i) { + auto val = tryTo(str); + if (val.hasValue()) { + doNotOptimizeAway(val.value()); + } + } +} + template 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 +inline void ptrPairToIntOptional(StringPiece sp, uint32_t n) { + for (uint32_t i = 0; i < n; ++i) { + auto val = tryTo(sp.begin(), sp.end()); + if (val.hasValue()) { + doNotOptimizeAway(val.value()); + } + } +} + constexpr uint32_t kArithNumIter = 10000; template @@ -944,6 +964,24 @@ inline size_t arithToArithClassic(const U* in, uint32_t numItems) { return kArithNumIter * numItems; } +template +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(*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 double2IntBad{{1e100, 1.25, 2.5, 100.00001}}; } \ BENCHMARK(stringTo##name##ClassicError, n) { \ stringToTypeClassic(fail, n); \ + } \ + BENCHMARK(stringTo##name##Optional, n) { \ + stringToTypeOptional(pass, n); \ + } \ + BENCHMARK(stringTo##name##OptionalError, n) { \ + stringToTypeOptional(fail, n); \ } #define PTR_PAIR_TO_INT_BENCHMARK(type, name, pass, fail) \ @@ -988,14 +1032,26 @@ std::array double2IntBad{{1e100, 1.25, 2.5, 100.00001}}; } \ BENCHMARK(ptrPairTo##name##ClassicError, n) { \ ptrPairToIntClassic(fail, n); \ + } \ + BENCHMARK(ptrPairTo##name##Optional, n) { \ + ptrPairToIntOptional(pass, n); \ + } \ + BENCHMARK(ptrPairTo##name##OptionalError, n) { \ + ptrPairToIntOptional(fail, n); \ } -#define ARITH_TO_ARITH_BENCHMARK(type, name, pass, fail) \ - BENCHMARK_MULTI(name##Classic) { \ - return arithToArithClassic(pass.data(), pass.size()); \ - } \ - BENCHMARK_MULTI(name##ClassicError) { \ - return arithToArithClassic(fail.data(), fail.size()); \ +#define ARITH_TO_ARITH_BENCHMARK(type, name, pass, fail) \ + BENCHMARK_MULTI(name##Classic) { \ + return arithToArithClassic(pass.data(), pass.size()); \ + } \ + BENCHMARK_MULTI(name##ClassicError) { \ + return arithToArithClassic(fail.data(), fail.size()); \ + } \ + BENCHMARK_MULTI(name##Optional) { \ + return arithToArithOptional(pass.data(), pass.size()); \ + } \ + BENCHMARK_MULTI(name##OptionalError) { \ + return arithToArithOptional(fail.data(), fail.size()); \ } #define INT_TO_ARITH_BENCHMARK(type, name, pass, fail) \ diff --git a/folly/test/ConvTest.cpp b/folly/test/ConvTest.cpp index 0ac0ed5a..2ff1ab08 100644 --- a/folly/test/ConvTest.cpp +++ b/folly/test/ConvTest.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -820,6 +821,14 @@ TEST(Conv, StringToBool) { EXPECT_EQ(buf5, sp5.begin()); } +TEST(Conv, Transform) { + const std::vector in{1, 2, 3}; + std::vector out(in.size()); + std::transform(in.begin(), in.end(), out.begin(), to); + const std::vector ref{"1", "2", "3"}; + EXPECT_EQ(ref, out); +} + TEST(Conv, FloatToInt) { EXPECT_EQ(to(42.0f), 42); EXPECT_EQ(to(-128.0f), int8_t(-128)); @@ -874,7 +883,7 @@ template 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("xxxx"); + EXPECT_FALSE(rv1.hasValue()); + auto rv2 = folly::tryTo("false"); + EXPECT_TRUE(rv2.hasValue()); + EXPECT_FALSE(rv2.value()); + auto rv3 = folly::tryTo("yes"); + EXPECT_TRUE(rv3.hasValue()); + EXPECT_TRUE(rv3.value()); +} + +TEST(Conv, TryStringToInt) { + auto rv1 = folly::tryTo("1000000000000000000000000000000"); + EXPECT_FALSE(rv1.hasValue()); + auto rv2 = folly::tryTo("4711"); + EXPECT_TRUE(rv2.hasValue()); + EXPECT_EQ(rv2.value(), 4711); +} + +TEST(Conv, TryStringToFloat) { + auto rv1 = folly::tryTo(""); + EXPECT_FALSE(rv1.hasValue()); + auto rv2 = folly::tryTo("3.14"); + EXPECT_TRUE(rv2.hasValue()); + EXPECT_NEAR(rv2.value(), 3.14, 1e-5); +} + +TEST(Conv, TryStringToDouble) { + auto rv1 = folly::tryTo(""); + EXPECT_FALSE(rv1.hasValue()); + auto rv2 = folly::tryTo("3.14"); + EXPECT_TRUE(rv2.hasValue()); + EXPECT_NEAR(rv2.value(), 3.14, 1e-10); +} + +TEST(Conv, TryIntToInt) { + auto rv1 = folly::tryTo(256); + EXPECT_FALSE(rv1.hasValue()); + auto rv2 = folly::tryTo(255); + EXPECT_TRUE(rv2.hasValue()); + EXPECT_EQ(rv2.value(), 255); +} + +TEST(Conv, TryFloatToFloat) { + auto rv1 = folly::tryTo(1e100); + EXPECT_FALSE(rv1.hasValue()); + auto rv2 = folly::tryTo(25.5f); + EXPECT_TRUE(rv2.hasValue()); + EXPECT_NEAR(rv2.value(), 25.5, 1e-10); +} + +TEST(Conv, TryFloatToInt) { + auto rv1 = folly::tryTo(100.001); + EXPECT_FALSE(rv1.hasValue()); + auto rv2 = folly::tryTo(100.0); + EXPECT_TRUE(rv2.hasValue()); + EXPECT_EQ(rv2.value(), 100); +} + +TEST(Conv, TryIntToFloat) { + auto rv1 = folly::tryTo(std::numeric_limits::max()); + EXPECT_FALSE(rv1.hasValue()); + auto rv2 = folly::tryTo(1000ULL); + EXPECT_TRUE(rv2.hasValue()); + EXPECT_EQ(rv2.value(), 1000.0f); +} + +TEST(Conv, TryPtrPairToInt) { + StringPiece sp1("1000000000000000000000000000000"); + auto rv1 = folly::tryTo(sp1.begin(), sp1.end()); + EXPECT_FALSE(rv1.hasValue()); + StringPiece sp2("4711"); + auto rv2 = folly::tryTo(sp2.begin(), sp2.end()); + EXPECT_TRUE(rv2.hasValue()); + EXPECT_EQ(rv2.value(), 4711); + StringPiece sp3("-4711"); + auto rv3 = folly::tryTo(sp3.begin(), sp3.end()); + EXPECT_TRUE(rv3.hasValue()); + EXPECT_EQ(rv3.value(), -4711); + StringPiece sp4("4711"); + auto rv4 = folly::tryTo(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(&in); - in.removePrefix("x"); - out.h = folly::to(&in); +Expected 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 @@ -1117,3 +1213,10 @@ TEST(Conv, custom_kkproviders) { EXPECT_GT(str.capacity(), 2000); EXPECT_LT(str.capacity(), 2500); } + +TEST(Conv, TryToThenWithVoid) { + auto x = tryTo("42").then([](int) {}); + EXPECT_TRUE(x.hasValue()); + Unit u = x.value(); + (void)u; +} diff --git a/folly/test/StringTest.cpp b/folly/test/StringTest.cpp index 76cf6ac4..c01cca5d 100644 --- a/folly/test/StringTest.cpp +++ b/folly/test/StringTest.cpp @@ -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 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) { diff --git a/folly/test/TraitsTest.cpp b/folly/test/TraitsTest.cpp index 23541b8e..402d1d2c 100644 --- a/folly/test/TraitsTest.cpp +++ b/folly/test/TraitsTest.cpp @@ -52,7 +52,7 @@ TEST(Traits, scalars) { TEST(Traits, containers) { EXPECT_TRUE (IsRelocatable>::value); - EXPECT_TRUE ((IsRelocatable>::value)); + EXPECT_TRUE((IsRelocatable>::value)); EXPECT_TRUE ((IsRelocatable>::value)); }