X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=blobdiff_plain;f=folly%2FConv.h;h=b37e1aa03979341aa630b42b59c312fa66fcd6f7;hp=6103849a26e121701a74fa283c4b26198891c7ee;hb=c98de6b2ecb4b4855fe6ce152329dc74eaae4710;hpb=d51ca1761cfc2dfca6e412eeee693f87e0974414 diff --git a/folly/Conv.h b/folly/Conv.h index 6103849a..b37e1aa0 100644 --- a/folly/Conv.h +++ b/folly/Conv.h @@ -1,5 +1,5 @@ /* - * Copyright 2016 Facebook, Inc. + * Copyright 2017 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ #pragma once #include +#include #include #include #include @@ -37,120 +38,148 @@ #include #include // V8 JavaScript implementation +#include +#include #include #include -#include #include +#include +#include +#include -#define FOLLY_RANGE_CHECK_STRINGIZE(x) #x -#define FOLLY_RANGE_CHECK_STRINGIZE2(x) FOLLY_RANGE_CHECK_STRINGIZE(x) +namespace folly { -// Android doesn't support std::to_string so just use a placeholder there. -#ifdef __ANDROID__ -#define FOLLY_RANGE_CHECK_TO_STRING(x) std::string("N/A") -#else -#define FOLLY_RANGE_CHECK_TO_STRING(x) std::to_string(x) -#endif +// 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 +}; -#define FOLLY_RANGE_CHECK(condition, message, src) \ - ((condition) ? (void)0 : throw std::range_error( \ - (std::string(__FILE__ "(" FOLLY_RANGE_CHECK_STRINGIZE2(__LINE__) "): ") \ - + (message) + ": '" + (src) + "'").c_str())) +struct ConversionErrorBase : std::range_error { + using std::range_error::range_error; +}; -#define FOLLY_RANGE_CHECK_BEGIN_END(condition, message, b, e) \ - FOLLY_RANGE_CHECK(condition, message, std::string((b), (e) - (b))) +class ConversionError : public ConversionErrorBase { + public: + ConversionError(const std::string& str, ConversionCode code) + : ConversionErrorBase(str), code_(code) {} -#define FOLLY_RANGE_CHECK_STRINGPIECE(condition, message, sp) \ - FOLLY_RANGE_CHECK(condition, message, std::string((sp).data(), (sp).size())) + ConversionError(const char* str, ConversionCode code) + : ConversionErrorBase(str), code_(code) {} -namespace folly { + ConversionCode errorCode() const { + return code_; + } + + private: + ConversionCode code_; +}; + +/******************************************************************************* + * Custom Error Translation + * + * Your overloaded parseTo() function can return a custom error code on failure. + * ::folly::to() will call makeConversionError to translate that error code into + * an object to throw. makeConversionError is found by argument-dependent + * lookup. It should have this signature: + * + * namespace other_namespace { + * enum YourErrorCode { BAD_ERROR, WORSE_ERROR }; + * + * struct YourConversionError : ConversionErrorBase { + * YourConversionError(const char* what) : ConversionErrorBase(what) {} + * }; + * + * YourConversionError + * makeConversionError(YourErrorCode code, ::folly::StringPiece sp) { + * ... + * return YourConversionError(messageString); + * } + ******************************************************************************/ +ConversionError makeConversionError(ConversionCode code, StringPiece sp); + +namespace detail { +/** + * Enforce that the suffix following a number is made up only of whitespace. + */ +inline ConversionCode enforceWhitespaceErr(StringPiece sp) { + for (auto c : sp) { + if (UNLIKELY(!std::isspace(c))) { + return ConversionCode::NON_WHITESPACE_AFTER_END; + } + } + return ConversionCode::SUCCESS; +} + +/** + * Keep this implementation around for prettyToDouble(). + */ +inline void enforceWhitespace(StringPiece sp) { + auto err = enforceWhitespaceErr(sp); + if (err != ConversionCode::SUCCESS) { + throw makeConversionError(err, sp); + } +} +} // namespace detail /** * The identity conversion function. - * to(T) returns itself for all types T. + * tryTo(T) returns itself for all types T. */ template -typename std::enable_if::value, Tgt>::type -to(const Src & value) { - return value; +typename std::enable_if< + std::is_same::type>::value, + Expected>::type +tryTo(Src&& value) { + return std::forward(value); } template -typename std::enable_if::value, Tgt>::type -to(Src && value) { +typename std::enable_if< + std::is_same::type>::value, + Tgt>::type +to(Src&& value) { return std::forward(value); } /******************************************************************************* - * Integral to integral + * Arithmetic to boolean ******************************************************************************/ /** - * Unchecked conversion from integral to boolean. This is different from the - * other integral conversions because we use the C convention of treating any + * 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_integral::value - && !std::is_same::value - && std::is_same::value, - Tgt>::type -to(const Src & value) { - return value != 0; + std::is_arithmetic::value && !std::is_same::value && + std::is_same::value, + Expected>::type +tryTo(const Src& value) { + return value != Src(); } -/** - * Checked conversion from integral to integral. The checks are only - * performed when meaningful, e.g. conversion from int to long goes - * unchecked. - */ template typename std::enable_if< - std::is_integral::value - && !std::is_same::value - && !std::is_same::value - && std::is_integral::value, - Tgt>::type -to(const Src & value) { - /* static */ if (std::numeric_limits::max() - < std::numeric_limits::max()) { - FOLLY_RANGE_CHECK( - (!greater_than::max()>(value)), - "Overflow", - FOLLY_RANGE_CHECK_TO_STRING(value)); - } - /* static */ if (std::is_signed::value && - (!std::is_signed::value || sizeof(Src) > sizeof(Tgt))) { - FOLLY_RANGE_CHECK( - (!less_than::min()>(value)), - "Negative overflow", - FOLLY_RANGE_CHECK_TO_STRING(value)); - } - return static_cast(value); -} - -/******************************************************************************* - * Floating point to floating point - ******************************************************************************/ - -template -typename std::enable_if< - std::is_floating_point::value - && std::is_floating_point::value - && !std::is_same::value, - Tgt>::type -to(const Src & value) { - /* static */ if (std::numeric_limits::max() < - std::numeric_limits::max()) { - FOLLY_RANGE_CHECK(value <= std::numeric_limits::max(), - "Overflow", - FOLLY_RANGE_CHECK_TO_STRING(value)); - FOLLY_RANGE_CHECK(value >= -std::numeric_limits::max(), - "Negative overflow", - FOLLY_RANGE_CHECK_TO_STRING(value)); - } - return boost::implicit_cast(value); + std::is_arithmetic::value && !std::is_same::value && + std::is_same::value, + Tgt>::type +to(const Src& value) { + return value != Src(); } /******************************************************************************* @@ -159,35 +188,57 @@ to(const 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 +283,7 @@ unsafeTelescope128(char * buffer, size_t room, unsigned __int128 x) { return p; } -} +} // namespace detail #endif /** @@ -252,7 +303,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, @@ -332,12 +383,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 +400,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 +408,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 +429,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 +440,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 +461,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 +534,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 +542,7 @@ estimateSpaceNeeded(T) { return detail::digitsEnough<__int128>(); } -template +template constexpr typename std::enable_if< std::is_same::value, size_t>::type @@ -515,9 +568,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 +667,7 @@ estimateSpaceNeeded(Src value) { namespace detail { constexpr int kConvMaxDecimalInShortestLow = -6; constexpr int kConvMaxDecimalInShortestHigh = 21; -} // folly::detail +} // namespace detail /** Wrapper around DoubleToStringConverter **/ template @@ -639,14 +694,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 +743,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 +753,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 @@ -719,7 +776,9 @@ estimateSpaceNeeded(const Src&) { namespace detail { -inline size_t estimateSpaceToReserve(size_t sofar) { +template +typename std::enable_if::value, size_t>::type +estimateSpaceToReserve(size_t sofar, Tgt*) { return sofar; } @@ -728,20 +787,16 @@ size_t estimateSpaceToReserve(size_t sofar, const T& v, const Ts&... vs) { return estimateSpaceToReserve(sofar + estimateSpaceNeeded(v), vs...); } -template -size_t estimateSpaceToReserve(size_t sofar, const T& v) { - return sofar + estimateSpaceNeeded(v); -} - -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) * estimateSpaceToReserve(0, d); + size_t fordelim = (sizeof...(vs) - 2) * + estimateSpaceToReserve(0, d, static_cast(nullptr)); getLastElement(vs...)->reserve(estimateSpaceToReserve(fordelim, vs...)); } @@ -757,11 +812,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...); @@ -775,11 +829,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 @@ -788,8 +841,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. @@ -813,11 +865,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...); } @@ -843,11 +894,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...); @@ -884,11 +932,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...); } @@ -897,11 +944,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...); @@ -916,25 +960,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); } /** @@ -943,10 +1010,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); @@ -959,205 +1027,235 @@ toDelim(const Delim& delim, const Ts&... vs) { namespace detail { -/** - * Finds the first non-digit in a string. The number of digits - * searched depends on the precision of the Tgt integral. Assumes the - * string starts with NO whitespace and NO sign. - * - * The semantics of the routine is: - * for (;; ++b) { - * if (b >= e || !isdigit(*b)) return b; - * } - * - * Complete unrolling marks bottom-line (i.e. entire conversion) - * improvements of 20%. - */ - template - const char* findFirstNonDigit(const char* b, const char* e) { - for (; b < e; ++b) { - auto const c = static_cast(*b) - '0'; - if (c >= 10) break; - } - return b; - } +Expected str_to_bool(StringPiece* src) noexcept; + +template +Expected str_to_floating(StringPiece* src) noexcept; +extern template Expected str_to_floating( + StringPiece* src) noexcept; +extern template Expected str_to_floating( + StringPiece* src) noexcept; - bool str_to_bool(StringPiece* src); - float str_to_float(StringPiece* src); - double str_to_double(StringPiece* src); +template +Expected digits_to(const char* b, const char* e) noexcept; + +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; + +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; + +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected digits_to( + const char*, + const char*) noexcept; + +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; + +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; + +#if FOLLY_HAVE_INT128_T +extern template Expected<__int128, ConversionCode> digits_to<__int128>( + const char*, + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; +#endif - template - Tgt digits_to(const char* b, const char* e); +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; +extern template Expected +str_to_integral(StringPiece* src) noexcept; - extern template unsigned char digits_to(const char* b, - const char* e); - extern template unsigned short digits_to(const char* b, - const char* e); - extern template unsigned int digits_to(const char* b, - const char* e); - extern template unsigned long digits_to(const char* b, - const char* e); - extern template unsigned long long digits_to( - const char* b, const char* e); #if FOLLY_HAVE_INT128_T - extern template unsigned __int128 digits_to(const char* b, - const char* e); +extern template Expected<__int128, ConversionCode> str_to_integral<__int128>( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; #endif -} // namespace detail +template +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, + Expected>::type +convertTo(StringPiece* src) noexcept { + return str_to_floating(src); +} + +template +typename std::enable_if< + std::is_integral::value && !std::is_same::value, + Expected>::type +convertTo(StringPiece* src) noexcept { + return str_to_integral(src); +} + +} // namespace detail /** * String represented as a pair of pointers to char to unsigned * integrals. Assumes NO whitespace before or after. */ -template +template typename std::enable_if< - std::is_integral::value && !std::is_signed::value - && !std::is_same::type, bool>::value, - Tgt>::type -to(const char * b, const char * e) { + std::is_integral::value && !std::is_same::value, + Expected>::type +tryTo(const char* b, const char* e) { return detail::digits_to(b, e); } -/** - * String represented as a pair of pointers to char to signed - * integrals. Assumes NO whitespace before or after. Allows an - * optional leading sign. - */ -template +template typename std::enable_if< - std::is_integral::value && std::is_signed::value, - Tgt>::type -to(const char * b, const char * e) { - FOLLY_RANGE_CHECK(b < e, "Empty input string in conversion to integral", - to("b: ", intptr_t(b), " e: ", intptr_t(e))); - if (!isdigit(*b)) { - if (*b == '-') { - Tgt result = -to::type>(b + 1, e); - FOLLY_RANGE_CHECK_BEGIN_END(result <= 0, "Negative overflow.", b, e); - return result; - } - FOLLY_RANGE_CHECK_BEGIN_END(*b == '+', "Invalid lead character", b, e); - ++b; - } - Tgt result = to::type>(b, e); - FOLLY_RANGE_CHECK_BEGIN_END(result >= 0, "Overflow", b, e); - return result; + std::is_integral::value && !std::is_same::value, + Tgt>::type +to(const char* b, const char* e) { + return tryTo(b, e).thenOrThrow( + [](Tgt res) { return res; }, + [=](ConversionCode code) { + return makeConversionError(code, StringPiece(b, e)); + }); } -namespace detail { -/** - * StringPiece to integrals, with progress information. Alters the - * StringPiece parameter to munch the already-parsed characters. - */ -template -Tgt str_to_integral(StringPiece* src) { - auto b = src->data(), past = src->data() + src->size(); - for (;; ++b) { - FOLLY_RANGE_CHECK_STRINGPIECE(b < past, - "No digits found in input string", *src); - if (!isspace(*b)) break; - } - - auto m = b; - - // First digit is customized because we test for sign - bool negative = false; - /* static */ if (std::is_signed::value) { - if (!isdigit(*m)) { - if (*m == '-') { - negative = true; - } else { - FOLLY_RANGE_CHECK_STRINGPIECE(*m == '+', "Invalid leading character in " - "conversion to integral", *src); - } - ++b; - ++m; - } - } - FOLLY_RANGE_CHECK_STRINGPIECE(m < past, "No digits found in input string", - *src); - FOLLY_RANGE_CHECK_STRINGPIECE(isdigit(*m), "Non-digit character found", *src); - m = detail::findFirstNonDigit(m + 1, past); - - Tgt result; - /* static */ if (!std::is_signed::value) { - result = detail::digits_to::type>(b, m); - } else { - auto t = detail::digits_to::type>(b, m); - if (negative) { - result = -t; - FOLLY_RANGE_CHECK_STRINGPIECE(is_non_positive(result), - "Negative overflow", *src); - } else { - result = t; - FOLLY_RANGE_CHECK_STRINGPIECE(is_non_negative(result), "Overflow", *src); - } - } - src->advance(m - src->data()); - return result; -} +/******************************************************************************* + * Conversions from string types to arithmetic types. + ******************************************************************************/ /** - * Enforce that the suffix following a number is made up only of whitespace. + * Parsing strings to numeric types. */ -inline void enforceWhitespace(StringPiece sp) { - for (char ch : sp) { - FOLLY_RANGE_CHECK_STRINGPIECE( - isspace(ch), to("Non-whitespace: ", ch), sp); - } +template +FOLLY_NODISCARD inline typename std::enable_if< + std::is_arithmetic::value, + Expected>::type +parseTo(StringPiece src, Tgt& out) { + return detail::convertTo(&src).then( + [&](Tgt res) { return void(out = res), src; }); } /******************************************************************************* - * Conversions from string types to floating-point types. + * Integral / Floating Point to integral / Floating Point ******************************************************************************/ - -} // namespace detail +namespace detail { /** - * StringPiece to bool, with progress information. Alters the - * StringPiece parameter to munch the already-parsed characters. + * 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. */ -inline void parseTo(StringPiece* src, bool& out) { - out = detail::str_to_bool(src); +template +typename std::enable_if< + !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); } /** - * 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. + * Checked conversion from integral to integral. The checks are only + * performed when meaningful, e.g. conversion from int to long goes + * unchecked. */ -template +template typename std::enable_if< - std::is_integral::type>::value>::type -parseTo(StringPiece* src, Tgt& out) { - out = detail::str_to_integral(src); -} - -inline void parseTo(StringPiece* src, float& out) { - out = detail::str_to_float(src); -} - -inline void parseTo(StringPiece* src, double& out) { - out = detail::str_to_double(src); + std::is_integral::value && !std::is_same::value && + !std::is_same::value && + std::is_integral::value, + Expected>::type +convertTo(const Src& value) noexcept { + /* static */ if ( + folly::_t>(std::numeric_limits::max()) < + folly::_t>(std::numeric_limits::max())) { + if (greater_than::max()>(value)) { + 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 makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW); + } + } + return static_cast(value); } -template +/** + * Checked conversion from floating to floating. The checks are only + * performed when meaningful, e.g. conversion from float to double goes + * unchecked. + */ +template typename std::enable_if< - std::is_floating_point::value || - std::is_integral::type>::value>::type -parseTo(StringPiece src, Tgt& out) { - parseTo(&src, out); - detail::enforceWhitespace(src); + std::is_floating_point::value && std::is_floating_point::value && + !std::is_same::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 makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW); + } + if (value < std::numeric_limits::lowest()) { + return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW); + } + } + return static_cast(value); } -/******************************************************************************* - * Integral to floating point and back - ******************************************************************************/ - -namespace detail { - /** * Check if a floating point value can safely be converted to an * integer value without triggering undefined behaviour. @@ -1174,7 +1272,7 @@ checkConversion(const Src& value) { if (value > tgtMaxAsSrc) { return false; } - const Src mmax = std::nextafter(tgtMaxAsSrc, Src()); + const Src mmax = folly::nextafter(tgtMaxAsSrc, Src()); if (static_cast(value - mmax) > std::numeric_limits::max() - static_cast(mmax)) { return false; @@ -1183,7 +1281,7 @@ checkConversion(const Src& value) { if (value < tgtMinAsSrc) { return false; } - const Src mmin = std::nextafter(tgtMinAsSrc, Src()); + const Src mmin = folly::nextafter(tgtMinAsSrc, Src()); if (static_cast(value - mmin) < std::numeric_limits::min() - static_cast(mmin)) { return false; @@ -1210,7 +1308,6 @@ constexpr typename std::enable_if< checkConversion(const Src&) { return true; } -} /** * Checked conversion from integral to floating point and back. The @@ -1219,30 +1316,58 @@ checkConversion(const Src&) { * complements existing routines nicely. For various rounding * routines, see . */ -template +template typename std::enable_if< - (std::is_integral::value && std::is_floating_point::value) - || - (std::is_floating_point::value && std::is_integral::value), - Tgt>::type -to(const Src & value) { - if (detail::checkConversion(value)) { - Tgt result = Tgt(value); - if (detail::checkConversion(result)) { - auto witness = static_cast(result); - if (value == witness) { + (std::is_integral::value && std::is_floating_point::value) || + (std::is_floating_point::value && std::is_integral::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 result; } } } - throw std::range_error( - to("to<>: loss of precision when converting ", value, + return makeUnexpected(ConversionCode::ARITH_LOSS_OF_PRECISION); +} + +template +inline std::string errorValue(const Src& value) { #ifdef FOLLY_HAS_RTTI - " to type ", typeid(Tgt).name() + return to("(", demangle(typeid(Tgt)), ") ", value); #else - " to other type" + return to(value); #endif - ).c_str()); +} + +template +using IsArithToArith = std::integral_constant< + bool, + !std::is_same::value && !std::is_same::value && + std::is_arithmetic::value && + std::is_arithmetic::value>; + +} // namespace detail + +template +typename std::enable_if< + 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)); + }); } /******************************************************************************* @@ -1253,49 +1378,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); }); } /******************************************************************************* @@ -1304,27 +1546,40 @@ 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)); } } // namespace folly - -// FOLLY_CONV_INTERNAL is defined by Conv.cpp. Keep the FOLLY_RANGE_CHECK -// macro for use in Conv.cpp, but #undefine it everywhere else we are included, -// to avoid defining this global macro name in other files that include Conv.h. -#ifndef FOLLY_CONV_INTERNAL -#undef FOLLY_RANGE_CHECK -#undef FOLLY_RANGE_CHECK_BEGIN_END -#undef FOLLY_RANGE_CHECK_STRINGPIECE -#undef FOLLY_RANGE_CHECK_STRINGIZE -#undef FOLLY_RANGE_CHECK_STRINGIZE2 -#endif