X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=blobdiff_plain;f=folly%2FConv.h;h=61e50e3733b316a4254c6bac094962efe2e80e93;hp=d6553373fa0203dfb120466ce42a0b1a50a454c8;hb=3d9eb7ffc32c2f5f878bb4aee61d30cb8c62a146;hpb=ad2f872bf354b62dbde66937da096565ef9ed663 diff --git a/folly/Conv.h b/folly/Conv.h index d6553373..61e50e37 100644 --- a/folly/Conv.h +++ b/folly/Conv.h @@ -1,5 +1,5 @@ /* - * Copyright 2016 Facebook, Inc. + * Copyright 2011-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,79 +35,92 @@ #include #include -#include #include // V8 JavaScript implementation #include +#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) {} + + ConversionError(const char* str, ConversionCode code) + : ConversionErrorBase(str), code_(code) {} - Code errorCode() const { return 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); } } +} // namespace detail /** - * 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,35 +187,57 @@ to(Src && value) { namespace detail { -template -const T& getLastElement(const T & v) { - return v; -} +#ifdef _MSC_VER +// MSVC can't quite figure out the LastElementImpl::call() stuff +// in the base implementation, so we have to use tuples instead, +// which result in significantly more templates being compiled, +// though the runtime performance is the same. -template -typename std::tuple_element< - sizeof...(Ts), - std::tuple >::type const& - getLastElement(const T&, const Ts&... vs) { - return getLastElement(vs...); +template +auto getLastElement(Ts&&... ts) -> decltype( + std::get(std::forward_as_tuple(std::forward(ts)...))) { + return std::get( + std::forward_as_tuple(std::forward(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. +inline void getLastElement() {} + +template +struct LastElementType : std::tuple_element> {}; + +template <> +struct LastElementType<0> { + using type = void; +}; + template -struct last_element { - typedef typename std::enable_if< - sizeof...(Ts) >= 1, - typename std::tuple_element< - sizeof...(Ts) - 1, std::tuple - >::type>::type type; +struct LastElement + : std::decay::type> {}; +#else +template +struct LastElementImpl { + static void call(Ignored...) {} }; -template <> -struct last_element<> { - typedef void type; +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...); +} + +template +struct LastElement : std::decay::call(std::declval()...))> { }; +#endif } // namespace detail @@ -232,7 +282,7 @@ unsafeTelescope128(char * buffer, size_t room, unsigned __int128 x) { return p; } -} +} // namespace detail #endif /** @@ -252,7 +302,7 @@ inline uint32_t digits10(uint64_t v) { // 10^i, defined for i 0 through 19. // This is 20 * 8 == 160 bytes, which fits neatly into 5 cache lines // (assuming a cache line size of 64). - static const uint64_t powersOf10[20] FOLLY_ALIGNED(64) = { + alignas(64) static const uint64_t powersOf10[20] = { 1, 10, 100, @@ -276,7 +326,7 @@ inline uint32_t digits10(uint64_t v) { }; // "count leading zeroes" operation not valid; for 0; special case this. - if UNLIKELY (! v) { + if (UNLIKELY(!v)) { return 1; } @@ -291,16 +341,24 @@ inline uint32_t digits10(uint64_t v) { // return that log_10 lower bound, plus adjust if input >= 10^(that bound) // in case there's a small error and we misjudged length. - return minLength + (uint32_t) (UNLIKELY (v >= powersOf10[minLength])); + return minLength + uint32_t(v >= powersOf10[minLength]); #else uint32_t result = 1; - for (;;) { - if (LIKELY(v < 10)) return result; - if (LIKELY(v < 100)) return result + 1; - if (LIKELY(v < 1000)) return result + 2; - if (LIKELY(v < 10000)) return result + 3; + while (true) { + if (LIKELY(v < 10)) { + return result; + } + if (LIKELY(v < 100)) { + return result + 1; + } + if (LIKELY(v < 1000)) { + return result + 2; + } + if (LIKELY(v < 10000)) { + return result + 3; + } // Skip ahead by 4 orders of magnitude v /= 10000U; result += 4; @@ -332,12 +390,12 @@ inline uint32_t uint64ToBufferUnsafe(uint64_t v, char *const buffer) { // Keep these together so a peephole optimization "sees" them and // computes them in one shot. auto const q = v / 10; - auto const r = static_cast(v % 10); + auto const r = static_cast(v % 10); buffer[pos--] = '0' + r; v = q; } // Last digit is trivial to handle - buffer[pos] = static_cast(v) + '0'; + buffer[pos] = static_cast(v) + '0'; return result; } @@ -349,7 +407,7 @@ void toAppend(char value, Tgt * result) { *result += value; } -template +template constexpr typename std::enable_if< std::is_same::value, size_t>::type @@ -357,13 +415,10 @@ estimateSpaceNeeded(T) { return 1; } -/** - * Ubiquitous helper template for writing string appenders - */ -template struct IsSomeString { - enum { value = std::is_same::value - || std::is_same::value }; -}; +template +constexpr size_t estimateSpaceNeeded(const char (&)[N]) { + return N; +} /** * Everything implicitly convertible to const char* gets appended. @@ -381,11 +436,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(); @@ -393,12 +447,18 @@ estimateSpaceNeeded(Src value) { return 0; } -template +template +typename std::enable_if::value, size_t>::type +estimateSpaceNeeded(Src const& value) { + return value.size(); +} + +template typename std::enable_if< - (std::is_convertible::value || - IsSomeString::value) && - !std::is_convertible::value, - size_t>::type + std::is_convertible::value && + !IsSomeString::value && + !std::is_convertible::value, + size_t>::type estimateSpaceNeeded(Src value) { return folly::StringPiece(value).size(); } @@ -408,7 +468,7 @@ inline size_t estimateSpaceNeeded(std::nullptr_t /* value */) { return 0; } -template +template typename std::enable_if< std::is_pointer::value && IsSomeString>::value, @@ -481,7 +541,7 @@ toAppend(unsigned __int128 value, Tgt * result) { result->append(buffer + p, buffer + sizeof(buffer)); } -template +template constexpr typename std::enable_if< std::is_same::value, size_t>::type @@ -489,7 +549,7 @@ estimateSpaceNeeded(T) { return detail::digitsEnough<__int128>(); } -template +template constexpr typename std::enable_if< std::is_same::value, size_t>::type @@ -515,9 +575,11 @@ toAppend(Src value, Tgt * result) { char buffer[20]; if (value < 0) { result->push_back('-'); - result->append(buffer, uint64ToBufferUnsafe(-uint64_t(value), buffer)); + result->append( + buffer, + uint64ToBufferUnsafe(~static_cast(value) + 1, buffer)); } else { - result->append(buffer, uint64ToBufferUnsafe(value, buffer)); + result->append(buffer, uint64ToBufferUnsafe(uint64_t(value), buffer)); } } @@ -612,7 +674,7 @@ estimateSpaceNeeded(Src value) { namespace detail { constexpr int kConvMaxDecimalInShortestLow = -6; constexpr int kConvMaxDecimalInShortestHigh = 21; -} // folly::detail +} // namespace detail /** Wrapper around DoubleToStringConverter **/ template @@ -639,14 +701,14 @@ toAppend( conv.ToShortest(value, &builder); break; case DoubleToStringConverter::FIXED: - conv.ToFixed(value, numDigits, &builder); + conv.ToFixed(value, int(numDigits), &builder); break; default: CHECK(mode == DoubleToStringConverter::PRECISION); - conv.ToPrecision(value, numDigits, &builder); + conv.ToPrecision(value, int(numDigits), &builder); break; } - const size_t length = builder.position(); + const size_t length = size_t(builder.position()); builder.Finalize(); result->append(buffer, length); } @@ -688,7 +750,9 @@ estimateSpaceNeeded(Src value) { // so 21 is the longest non-exponential number > 1. detail::kConvMaxDecimalInShortestHigh }); - return kMaxPositiveSpace + (value < 0); // +1 for minus sign, if negative + return size_t( + kMaxPositiveSpace + + (value < 0 ? 1 : 0)); // +1 for minus sign, if negative } /** @@ -696,13 +760,13 @@ estimateSpaceNeeded(Src value) { * for estimateSpaceNeed for your type, so that we allocate * as much as you need instead of the default */ -template +template struct HasLengthEstimator : std::false_type {}; template constexpr typename std::enable_if< !std::is_fundamental::value -#ifdef FOLLY_HAVE_INT128_T +#if FOLLY_HAVE_INT128_T // On OSX 10.10, is_fundamental<__int128> is false :-O && !std::is_same<__int128, Src>::value && !std::is_same::value @@ -730,12 +794,12 @@ size_t estimateSpaceToReserve(size_t sofar, const T& v, const Ts&... vs) { return estimateSpaceToReserve(sofar + estimateSpaceNeeded(v), vs...); } -template +template void reserveInTarget(const Ts&...vs) { getLastElement(vs...)->reserve(estimateSpaceToReserve(0, vs...)); } -template +template void reserveInTargetDelim(const Delimiter& d, const Ts&...vs) { static_assert(sizeof...(vs) >= 2, "Needs at least 2 args"); size_t fordelim = (sizeof...(vs) - 2) * @@ -755,11 +819,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...); @@ -773,11 +836,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 @@ -786,8 +848,7 @@ toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) { toAppend(delim, detail::getLastElement(vs...)); toAppendDelimStrImpl(delim, vs...); } -} // folly::detail - +} // namespace detail /** * Variadic conversion to string. Appends each element in turn. @@ -811,11 +872,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...); } @@ -841,11 +901,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...); @@ -882,11 +939,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...); } @@ -895,11 +951,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...); @@ -914,25 +967,48 @@ 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); return result; } +/** + * Special version of to for floating point. When calling + * folly::to(double), generic implementation above will + * firstly reserve 24 (or 25 when negative value) bytes. This will + * introduce a malloc call for most mainstream string implementations. + * + * But for most cases, a floating point doesn't need 24 (or 25) bytes to + * be converted as a string. + * + * This special version will not do string reserve. + */ +template +typename std::enable_if< + IsSomeString::value && std::is_floating_point::value, + Tgt>::type +to(Src value) { + Tgt result; + toAppend(value, &result); + return result; +} + /** * toDelim(SomeString str) returns itself. */ template -typename std::enable_if::value && - std::is_same::value, - Tgt>::type -toDelim(const Delim& /* delim */, const Src& value) { - return value; +typename std::enable_if< + IsSomeString::value && + std::is_same::type>::value, + Tgt>::type +toDelim(const Delim& /* delim */, Src&& value) { + return std::forward(value); } /** @@ -941,10 +1017,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); @@ -957,130 +1034,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( - const char*, - const char*); -extern template ConversionResult digits_to( +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(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 /** @@ -1089,12 +1157,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)); + }); } /******************************************************************************* @@ -1102,49 +1180,37 @@ 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_NODISCARD 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 ******************************************************************************/ +namespace detail { + /** - * 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. + * Bool to integral/float doesn't need any special checks, and this + * overload means we aren't trying to see if a bool is less than + * an integer. */ -template +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(); + !std::is_same::value && + (std::is_integral::value || std::is_floating_point::value), + Expected>::type +convertTo(const bool& value) noexcept { + return static_cast(value ? 1 : 0); } -namespace detail { - /** * Checked conversion from integral to integral. The checks are only * performed when meaningful, e.g. conversion from int to long goes @@ -1155,22 +1221,23 @@ 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()) { + folly::_t>(std::numeric_limits::max()) < + folly::_t>(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); } /** @@ -1182,18 +1249,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 static_cast(value); } /** @@ -1260,18 +1327,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 @@ -1294,13 +1361,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)); + }); } /******************************************************************************* @@ -1311,49 +1385,166 @@ 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_NODISCARD 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_NODISCARD +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_NODISCARD +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_NODISCARD +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< + IsSomeString::value && !std::is_same::value, + Tgt>::type +to(Src const& src) { + return to(StringPiece(src.data(), src.size())); +} + +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; + Tgt 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); }); } /******************************************************************************* @@ -1362,15 +1553,39 @@ 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 && + !std::is_convertible::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_convertible::valuea && + std::is_enum::value && !std::is_same::value, + Expected>::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 && + !std::is_convertible::value, + Tgt>::type +to(const Src& value) { return to(static_cast::type>(value)); } template typename std::enable_if< - std::is_enum::value && !std::is_same::value, Tgt>::type -to(const Src & value) { + !std::is_convertible::value && std::is_enum::value && + !std::is_same::value, + Tgt>::type +to(const Src& value) { return static_cast(to::type>(value)); }